-
Redis의 Pub/Sub을 이용한 채팅방 기능 구현대용량 데이터 & 트래픽/대용량 처리를 위한 redis 2024. 3. 11. 14:34
Pub/Sub 패턴
- 메시징 모델 중의 하나로 발행과 구독 역할로 개념화한 형태
- 발행자와 구독자는 서로에 대한 정보 없이 특정 주제를 매개로 송수신
메시징 미들웨어의 장점
- 비동기 통신 가능
- 결합도가 낮음 - 개인끼리 직접 의존하지 않고 공통 미들웨어에 의존
메시징 미들웨어의 제품들로는 대표적으로 Kafka, RabbitMQ, ActiveMQ 등이 있다.
각각의 장점이 있기 때문에 상황에 맞게 사용해야 한다.
Redis의 Pub/Sub의 특징은 Queue에 메시지가 저장되지 않는다는 점이 있다. 즉, 온라인으로 연결된 subsriber들에게만 메시지가 전달된다. 또한 Kafka의 컨슈머 그룹같은 분산처리 개념이 없기 때문에 subscriber가 늘어날수록 성능이 저하된다.
Redis의 Pub/Sub이 필요한 상황
- 실시간으로 빠르게 전송되어야 하는 상황
- 메시지 유실을 감내할 수 있는 상황 (Queue에 저장되지 않기 때문에)
- Subscriber들이 다양한 채널을 유동적으로 바꾸면서 한시적으로 구독하는 상황
채팅방 기능의 요구사항
채팅 클라이언트와 채팅 서버가 통신 방식을 정하고 채팅 서버는 채팅방 관리 로직을 작성해야 한다.
Redis Pub/Sub을 이용한 채팅방 구현에서는 채팅방을 생성할 필요가 없이 채널을 사용하면 된다. 또한, 채팅방 접속자 관리도 할 필요가 없는데, 특정 채널에 메시지를 보내면 그 채널을 subscribe 하고있는 사용자들에게 보내면 되기 때문에 채팅방과 접속자 관리를 할 필요가 없다. 채팅방 메시지 수신/전송도 redis Pub/Sub에서 제공하는 기능 때문에 구현할 필요가 없어서 client 쪽만 구현해주면 된다.
MainClass
@SpringBootApplication public class PubSubChatApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(PubSubChatApplication.class, args); } @Override public void run(String... args) throws Exception { System.out.println("Application started"); } }
웹에서 하면 프론트엔드 코드도 작성해야하고 매우 복잡하기 때문에 커맨드라인을 통해 진행하겠다.
RedisConfig
@Configuration public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(); } // LettuceConnection을 반환하는 RedisConnection 생성 @Bean RedisMessageListenerContainer redisContainer() { final RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory()); return container; } // 메시지 구독을 처리해주고, 수신된 메시지를 처리할 수 있게 해줌 }
RedisConfig에는 주석에 적은 것처럼 LettuceConnection을 빈으로 등록해준 다음에 그걸 redisContainer에 넣어주고 return 한다.
ChatService
@Service public class ChatService implements MessageListener { @Autowired private RedisMessageListenerContainer container; @Autowired 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()); } // 이거를 통해 메시지가 올때마다 출력해준다. }
다음은 ChatService 클래스이다. 위의 RedisConfig에서 빈으로 등록한 ListnerContainer를 @Autowired로 받아왔다. enterChatRoom은 채팅을 입력할 수 있는 기능이다. messageLister에 해당 프로세스를 channelTopic으로 넣은 다음에 채팅을 입력하는 기능이다. onMessage 메서드는 채팅방을 구독하고 있는 사용자의 입장에서 다른 사람이 메시지를 보내면 Message가 출력되게 하는 메서드이다.
@SpringBootApplication public class PubSubChatApplication implements CommandLineRunner { @Autowired 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"); } }
main class에서 실행시키면 자동으로 chat1 채팅방으로 들어가게 했다.
bash를 4개 켜서 각각 스프링을 실행시켰더니 서로의 채팅이 각각의 채팅방에 보이게 되었다.
웹으로 구현해본 것이 아니고 커맨드라인을 이용했던 것이지만 redis의 Pub/Sub을 응용하면 실시간으로 채팅을 하거나 알림을 보내는 기능으로 사용할 수 있을 것이다.
'대용량 데이터 & 트래픽 > 대용량 처리를 위한 redis' 카테고리의 다른 글
redis의 Sorted Sets을 활용한 리더보드 구현 (0) 2024.03.11 서비스 속도를 높이는 redis의 캐시레이어 (0) 2024.03.10 분산 환경에서의 session 스토어 (0) 2024.03.10 Redis의 data type (0) 2024.03.10 In-memory DB인 Redis의 특징 (0) 2024.03.10