Pub-Sub을 이용한 채팅방 구현

Pub/Sub 패턴

  • 메시징 모델 중의 하나로 발행(Publish)과 구독(Subscribe) 역할로 개념화 한 형태
  • 발행자와 구독자는 서로에 대한 정보 없이 특정 주제(토픽 or 채널)를 매개로 송수신 Alt text

메시징 미들웨어 사용의 장점

  • 비동기: 통신의 비동기 처리
  • 낮은 결합도: 송신자와 수신자가 직접 서로 의존하지 않고 공통 미들웨어에 의존
  • 탄력성: 구성원들간에느슨한 연결로 인해 일부 장애가 생겨도 영향이 최소화됨
    (ex 메시징 미들웨어제품들: Kafka, RabbitMQ, ActiveMQ, … )

Redis의 Pub/Sub 특징

  • 메시지가 큐에 저장되지 않음.
  • Kafka의 컨슈머 그룹같은 분산처리 개념이 없음.
  • 메시지 발행 시 push 방식으로 subscriber들에게 전송
  • subscriber가 늘어날수록 성능이 저하 Alt text

Redis의 Pub/Sub의 유즈케이스

  • 실시간으로 빠르게 전송되어야 하는 메시지
  • 메시지 유실을 감내할 수 있는 케이스
  • 최대 1회 전송(at-most-once) 패턴이 적합한 경우
  • Subscriber들이 다양한 채널을 유동적으로 바꾸면서 한시적으로 구독하는 경우

채팅방 기능의 요구사항

  • 채팅 클라이언트와채팅 서버가 존재하고 통신 방식을 정해야 함.(프로토콜)
  • 채팅 서버는 채팅방 관리 로직을 작성해야 함 Alt text

Redis Pub/Sub을 이용한 채팅방 구현

  • 채팅방 기능을 publish/subscribe 구조를 이용해 쉽게 구현 Alt text

구현해보자!!

콘솔을 통한 채팅 구현

PubSubChatApplication.java

  • implements CommandLineRunner
  • run

RedisConfig.java

  • redisConnectionFactory
  • redisContainer

ChatService.java

  • implements MessageListener
  • onMessage
  • enterChatRoom

application.yml

spring:
  redis:
    host: 58.141.14.108
    port: 6379

PubSubChatApplication.java

@SpringBootApplication
public class PubSubChatApplication implements CommandLineRunner {

    @Autowired
    private ChatService chatService;

    public static void main(String[] args) {
        SpringApplication.run(PubSubChatApplication.class, args);
    }


    @Override
    public void run(String... args) throws Exception {
        System.out.println("Application started..");
        chatService.enterChatRoom("chat1");
    }
}

RedisConfig.java

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    RedisMessageListenerContainer redisContainer() {
        final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory());
        return container;
    }
}

ChatService.java

@Service
public class ChatService implements MessageListener {

    @Autowired
    private RedisMessageListenerContainer container;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    public void enterChatRoom(String chatRoomName){
        container.addMessageListener(this, new ChannelTopic(chatRoomName));

        Scanner in = new Scanner(System.in);
        while(in.hasNextLine()) {
            String line = in.nextLine();
            if(line.equals("q")) {
                System.out.println("Quit..");
                break;
            }

            redisTemplate.convertAndSend(chatRoomName, line);
        }

        container.removeMessageListener(this);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println("Message: " + message.toString());
    }
}

결과

local 3번 입력

Alt text

local 5번 출력

Alt text

redis-cli에서 subscribe 조회 테스트

1번 local 입력

Alt text

redis 서버 subscribe 조회

Alt text


Git link


© 2023 Lee. All rights reserved.