DB/Redis

Pub/Sub 패턴으로 채팅방 구현

Code Maestro 2023. 8. 11. 23:02
728x90

1. Pub/Sub 패턴

메시징 모델 중 하나로 발행(Publish)과 구독(Subscribe) 역할을 개념화 한 패턴이다. 

 

발행자와 구독자는 서로에 관한 정보 없이 특정 토픽을 매개로 송수신한다.

 

2. 메시징 미들웨어와 Redis의 Pub/Sub 차이점

보통 kafaka, RabbitMQ, ActiveMQ 같은 메시징 미들웨어의 장점은 낮은 결합도로 직접 서로 의존치 않고 미들웨어에 의존한다. 두 번째는 탄력성으로 느슨한 연결로 인해 장애가 일부 생겨도 영향이 최소화된다. 세 번째는 통신을 비동기 처리해서 API 호출을 제거하여 처리 시간을 감소한다. 

 

반면 Redis의 Pub/Sub은 메시지가 큐에 저장하지 않고, kafaka의 컨슈머 그룹 같은 분산 처리 개념이 없다. 또한 메시지 발행 시 push 방식으로 구독자들에게 전송하고, 구독자가 늘어날수록 성능이 저하된다. 

 

보통 언제 사용하냐면 실시간으로 빠르게 전송해야 하는 메시지나, 메시지 유실을 감수할 수 있는 케이스, 최대 1회 전송 패턴이 적합하고, 구독자들이 다양한 채널을 유동적으로 바꿔서 한시적으로 구독하는 경우에 사용된다. 

 

3. Redis로 구현하기

 

채팅방 기능을 publish/subscribe 구조로 구현해 보겠다. 

 

Client의 역할은 아래와 같다.

  • 채팅방 입장: 채널 구독으로 구현.
  • 메시지 전송: 채널에 publish
  • 메시지 수신: 채널 구독에 따른 listener 구현.

 

Chat Server의 역할은 아래와 같다.

  • 채팅방 생성: 채널 사용.
  • 접속자 관리: 필요 없음.
  • 메시지 수신/전송: 필요 없음.

 

@SpringBootApplication
public class RedisPracticeApplication implements CommandLineRunner {

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

    @Override
    public void run(String... args) throws Exception {
        System.out.println("애플리케이션 시작.");
    }
}

먼저 메인 클래스에 commandLineRunner를 넣어주고, override를 받아온다. 

@Configuration
public class RedisConfig {

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

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

pub/sub를 구현하기 위해서는 listner를 구현해야 한다. config를 통해 redisContainer를 생성해 보자.

 

@Service
public class ChatService implements MessageListener {

    @Autowired
    private RedisMessageListenerContainer container;

    public void enter(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("종료");
                break;
            }
        }
    }

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

enter 메서드를 통해 채팅방에 들어갈 수 있다. 사용자가 q를 눌리면 채팅방에 나갈 수 있게끔 만들었다.

 

@SpringBootApplication
public class RedisPracticeApplication implements CommandLineRunner {

    @Autowired
    private ChatService chatService;

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

    @Override
    public void run(String... args) throws Exception {
        System.out.println("애플리케이션 시작.");

        chatService.enter("chat1");
    }
}

메인 클래스에 관련 로직을 넣었다. 이러면 애플리케이션이 시작하자마자 chatService가 실행된다. 

 

ChatService의 onMessage를 통해 비동기로 메시지가 도착할 때마다 화면에 출력된다. 

 

스프링을 실행하고, Redis client에 publish 방이름 메시지를 넣어주면

 

위처럼 메시지가 계속 전송되는 걸 확인할 수 있다. 

 

이제 이 애플리케이션이 메시지를 전송할 수 있게 해 보겠다. 

 

@Service
public class ChatService implements MessageListener {

    @Autowired
    private RedisMessageListenerContainer container;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    public void enter(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("종료");
                break;
            }

            redisTemplate.convertAndSend(chatRoomName,line);
        }

        container.removeMessageListener(this);
    }

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

사용자가 한 줄 입력할 때마다 topic으로 전송하게 된다. 

이제 Redis에서 subscribe를 해보고 메시지를 받아오자.

 

애플리케이션에서 한글 메시지는 깨지고, 영문자는 제대로 전송되는 걸 볼 수 있다. 

 

참고로 만든 애플리케이션을 빌드하고 cmd 창으로 여러 개를 동시에 띄어도 같은 채팅방 안의 메시지가 동시에 출력 혹은 전송되는 걸 확인할 수 있다. 

 

 

이렇게 Redis pub/sub으 chat 시스템을 쉽게 구현할 있다. 이를 응용하여 알람, notification, 서버들간에 메시징 등 다양하게 활용할 수 있다. 

728x90