[혼자왔니] 채팅 서버 구현을 통해 알아본 redis와 kafka의 차이점

Update:     Updated:

카테고리:

태그:

채팅기능이 필요하다..

예를 들어 같이 배달을 시킬 사람을 모집하는 글을 올렸다면,
언제 모일건지, 어떤 메뉴를 시킬 건지, 결제는 어떻게 할 것인지 등등
모인 사람들끼리 대화를 할 수 있는 공간이 필요했다.
해당 기능 추가를 위해 채팅용 서버를 토이 프로젝트로 진행해보았다.

pub/sub 구조란?

메시지를 공급하는 주체와 소비하는 주체를 분리하여 제공하는 메시징 방법

image

  • 채팅방(우체통)을 생성한다: pub/sub 구현을 위한 Topic이 하나 생성된다.
  • 채팅방(우체통)에 입장한다: Topic을 구독한다.
  • 채팅방에서 메시지를 보내고 받는다: 해당 Topic으로 메시지를 발송하거나(pub-집배원) 메시지를 받는다(sub-구독자)

Web Socket과 STOMP

image

  • Web Socket
    • Websocket만을 이용해 채팅을 구현하면 해당 메시지가 어떤 요청인지, 어떻게 처리해야 되는지에 따라 채팅룸과 세션을 일일이 구현하고 메시지 발송 부분을 관리하는 추가 코드를 구현해 줘야한다.
  • STOMP
    • 메시징 전송을 효율적으로 하기 위해 나온 프로토콜
    • pub/sub 구조로 되어있어 메시지를 발송하고, 메시지를 받아 처리하는 부분이 명확이 구분되어 있어 개발 시 명확히 인지하고 개발하기 쉽다는 이점이 있음
    • 통신 메시지의 헤더에 값을 세팅할 수 있어 헤더 값을 기반으로 통신 시 인증처리를 구현하는 것도 가능함

채팅 서비스의 고도화

web socket과 STOMP 만을 이용해서 만든 채팅 서버를 고도화시켜보자..!

  • 서버를 재시작 할때마다 채팅방 정보들이 리셋된다.
    • 채팅방의 저장소가 없으므로 서버의 메모리에 적재된 채팅방은 서버를 재시작할 때마다 초기화되는 이슈가 있다.
      • DB 혹은 다른 저장소를 이용하여 채팅방이 계속 유지되도록 처리가 필요함(redis)
  • 채팅서버가 여러대일 때 서버간 채팅방 공유가 불가능함
    • websocketSTOMP pub/sub 을 이용하여 구현할 시, pub/sub이 발생한 서버 내에서만 메시지를 주고받는 것이 가능함
      • 채팅방(topic)이 생성된 서버 안에서만 유효하므로 다른 서버로 접속한 클라이언트는 접근 및 구독이 불가능하다.
    • 즉, 구독 대상(채팅방: topic)이 여러 서버에서 접근할 수 있도록 개선이 필요하다. (redis, kafka, rabbitMQ 등등)

두 가지 방법으로 구현해본 채팅 서버

spring boot와 web socket 및 message queue를 이용하여 채팅 서버를 만들어봤다.
구체적인 구현 방법은 잘 정리해놓은 블로그를 reference에 남겨놓는 것으로 하고,
redis, kafka 이렇게 총 2가지 message queue를 이용해서 구현해보면서 느꼈던 차이점에 대해서 정리해보려고 한다.

전반적인 동작 과정은 다음과 같다.

  1. 채팅방을 생성하면, redis 저장소에 채팅방 정보를 저장
  2. 채팅방에 입장하면 web socket 연결이 수행되고, 해당 채팅방에 대한 subscribe(구독)을 수행한다.
  3. 해당 채팅방에 message request를 보내면, message를 받아서 message queue(redis or kafka)에 publish 한다.
  4. message queue(redis or kafka)에 대한 subscriberweb socket 구독자에게 해당 채팅 메시지를 보낸다.

Redis를 사용하여 구현

image

redis를 사용할 경우,
message가 publish 되면 해당 channel을 구독하고 있는 모든 subscriber에게 message를 보내주게된다.
따라서, 각기 다른 채팅 서버를 통해 접속했더라도 같은 채팅방에서 서로 대화할 수 있는 것이다.

다만, redis는 보낸 message를 따로 저장하지는 않기 때문에, 해당 channelsubscribe하고 있지 않다면, message가 유실될 수 있다는 단점이 있다.

[8080 서버: 짱구 & 철수, 8090 서버: 맹구]
image

Kafka를 사용하여 구현

image

kafka를 사용할 경우,
message가 publish 되면 해당 message는 topic의 각 partition에 분산되어 저장된다.
따라서, redis와는 다르게 message의 유실에서는 더 안전하다고 볼 수 있다.

또, kafka의 경우 consumer가 message를 가져오는 방식으로 동작한다.
각 message에 offset을 할당해서 할당된 offset을 기반으로 consumer는 다음으로 가져올 메시지의 위치를 지정하여 메시지를 가져오게 된다.
이러한 특징 때문에 메시지의 순서를 보장할 수 있다.

자, 여기까지만 알고, 기존에 짰던 redis 기반의 채팅 서버를 kafka로 바꾸니까 원하는대로 동작하지 않았다.

뭐가 문제일까..

각각 8080과 8090포트로 서버를 띄워 접속해봤고, 채팅 메시지가 제대로 전달되지 않는 것을 확인할 수 있었다.

[8080 서버: 짱구 & 철수, 8090 서버: 맹구]
image

그리고 medium에서 왜 그런 것인지 확인할 수 있었다.

redis와 kafka의 동작 방식 차이

rediskafka와는 달리, channel에 도착한 message를 해당 channel을 구독하고 있는 ‘모든’ subscriber들에게 쏴 줄 수 있다.
따라서, 각각 다른 서버여도 같은 channel을 구독하고 있기만 하면 message를 받아볼 수 있는 것이다.

medium에서는 이를 TV 채널을 시청하는 것에 비유하였다.
해당 방송사(producer)에서 방영하는 라이브 방송은 해당 채널을 시청 중일 때, 같은 시간대에 같은 채널의 시청자들은 모두 같은 방송을 볼 수 있는 것에 비유했다.

하지만 kafka는 다르다.
topic에 message가 도착하면, 이를 소비하는 것은 consumer group 단위로 이루어진다.
즉, consumer 들이 같은 group에 속해 있다면, message는 해당 group에 있는 어느 consumer 중 하나에 의해서 소비된다는 것이다.

medium에서는 이를 우체통에 비유하였다.
철수(producer)가 편지를 써서 우체통(topic)에 넣어두었다면, 해당 편지는 누군가에 의해 발견될 때까지는 남아있을 것이고,
가족(consumer group) 중 어느 누군가에 의해 발견될 수 있을 것이다.
만약 영희네 오빠가 먼저 발견했다면, 영희는 편지의 존재조차 모르게 되는 것이다.

그럼 어떻게 해야할까..?

애초에 message queue를 둔 이유도, scale-out 등의 이유로 서버가 여럿이 있을 때 해결하려고 도입했던 것인데, kafka에서는 어떻게 해결할 수 있을까?

image
이 말에서 힌트를 얻었다.

image
위의 그림처럼 서버 별로 group id를 다르게 해주었다.
아쉽게도 동적으로 할당한 것은 아니지만, group id를 서버별로 다르게 하고, 빌드하여 테스트를 진행해보았다.

[8080 서버: 짱구 & 철수, 8090 서버: 맹구]
image


(코드 보러가기)
https://github.com/hi-june/chat-demo

Reference

1) https://www.daddyprogrammer.org/post/4077/spring-websocket-chatting/ [websocket으로 채팅 서버 만들기] 2) https://medium.com/frientrip/pub-sub-%EC%9E%98-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-de9dc1b9f739 [pub/sub 잘 알고 쓰자!]

RUAlone 카테고리 내 다른 글 보러가기

댓글 남기기