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, 서버들간에 메시징 등 다양하게 활용할 수 있다.
'DB > Redis' 카테고리의 다른 글
Redis 클러스터 (0) | 2023.08.17 |
---|---|
Redis 백업과 장애 복구 (0) | 2023.08.14 |
Sorted Sets으로 리더보드 구현 (0) | 2023.08.11 |
Redis로 캐시 레이어 구현 (0) | 2023.08.11 |
Redis를 이용한 세션 구현 (0) | 2023.08.10 |