Redis pub/sub

민재원·2022년 3월 23일
1

Redis

목록 보기
2/2

Intro

이전 포스트에서 Redis Streams 데이터 구조를 알아봤다.
이제는 publish subscribe 패턴에 대해 알아보고 redis pubsub의 내부 자료구조에 대해 알아보도록 하겠다.

publish subscribe pattern?

만약 쇼핑몰에서 새로운 고객이 회원가입을 했을때 쿠폰을 발행해주거나 가입축하 메일을 보낸다고 가정하자.하나의 서비스에서 구현한다면 순서대로 이벤트를 발생시키면 되겠지만 그게 여러 서비스를 운영하는 microservice라면 internal API를 통해 각각 이벤트를 보내야하는 수고가 있다. 그냥 유저가 생성되면 알아서 각자 서비스에서 탐지해 이벤트 처리를 하면 좋지 않을까? 해서 나온게 pub sub pattern이다. 유저가 생성되면 유저생성 channelpublish 하면 끝이고 유저 생성에 맞는 이벤트가 필요한 서비스에서는 유저생성 channelsubscribe 후 그에 맞는 이벤트 처리를 각 서비스에서 하면 된다.

publish subscribe를 직역하면 발행, 구독이고 당연히 publish를 하는 publisher와 subscribe를 하는 subscriber가 있다. publisher는 이벤트가 생겼을때 이벤트 메세지와 함께 채널에 publish를 하고 subscriber는 그 채널을 구독하고 메세지를 받아 이벤트 처리를 한다. 대표적인 publish subscribe 패턴을 사용하는 방법으로는 kafka와 redis의 pub/sub이 있다.

Kafka vs Redis

Kafka는 스트리밍 데이터를 다루기 위한 pub/sub 패턴을 다룰 수 있는 플랫폼 이고 Redispub/sub은 그저 자료구조(구조체)에 불과하다. 아직까지 뭐가 다른지 모를 수 있는데 당연하다. 일단은 pub/sub을 다루는데 있어서 kafka가 훨씬 무겁다고 이해하면 된다.

Kafka

Kafkapub/sub 패턴에서는 producerconsumer라는 개념이 존재한다.

kafkaproducer/consumer (publisher/subscriber)가 있는데 producerTopic에 이벤트를 보내고 이 이벤트는 Topic의 각 Partition에 분산되어 저장된다. Topic을 구독하고 있는 Consumer group 내의 Consumer는 각각 1개 이상의 partition으로부터 이벤트를 가져온다. 만약 partition 개수보다 consumer개수가 많다면 아무 일도 하지 않는 consumer가 생기기 때문에 항상 partition의 수를 consumer의 수 이상으로 해주는게 좋다.

Kafkatopic은 우체통과 같다. producer가 편지를 써서 우체통에 넣어놓으면 언젠가 consumer가 찾아간다. 찾아가고나면 편지는 이미 가져간 상태이고 만약 우체통이 없다면 편지를 받지 못한다. 우체통에서도 가족이 여러명이라면 바구니를 여러개두어 어머니의 바구니 아버지의 바구니를 추가로 놓는다면 각각의 바구니는 하나의 partition이 될 것 이다.

Redis.pubsub

Redispubsub은 그룹이라는 개념이 존재하지 않고 각 subscriberchannel을 구독하고 있다. 여기서 kafka와의 가장 큰 차이점으로 channel은 이벤트를 저장하지 않는다. 이벤트가 도착했을때 채널의 subscriber가 없다면 이벤트는 증발한다. 또한 응답을 받지 못하기 때문에 완벽한 이벤트의 성공을 보장하지는 못한다.

만약 user 서비스에 유저가 생성되어서 product 서비스에 장바구니 객체를 하나 만들어야한다. 또한 product 서비스에 트래픽이 몰려서 product 서비스를 세개로 증설했다. 만약 기본적인 설정으로 kafka를 사용한다면 유저가 생성되었을때 각 product 서비스에서 하나의 partition을 할당받았을 것 이기 때문에 하나의 장바구니만 생긴다. 하지만 redis는 세개의 장바구니가 생겨서 데이터 무결성에 문제가 생기게 된다. 이럴때 채널 그룹을 생성하면 redis에서도 문제될게 없지만 내가 말하고 싶은건 원리를 잘 이해하고 사용해야한다는 것 이다.

물론 kafka는 redis보다 더 다양한 기능이 있고 세밀하게 성능및 기능을 조정할 수 있다. 그러나 redis도 kafka처럼 사용할 수 있으므로 이벤트큐가 무겁고 core하게 pub/sub기능이 필요하다면 kafka를 추천하고 단순한 이벤트큐로 가볍게 사용하려고 한다면 redis를 추천한다.

redis-cli


사용법은 간단하다.

$ brew install redis

$ brew services start redis

$ redis-cli
## subscriber
> subscribe {channel_name}

## publisher
> publish {channel_name} {message}

redis.pubsub 내부구조

Redis의 pubsub은 구조체라고 intro에서 말했었다.
redis의 수많은 구조체중 하나고 publish를 담당하는 서버와 subscribe를 하는 클라이언트로 나눠져있다. 또한 channel 이름을 pattern으로 등록할 수 있다,

  • PUBLISH channel_name
  • SUBSCRIBE channel_name
  • PSUBSCRIBE pattern_name

server.pubsub_channels → server측에서 chennal을 저장하는 구조


pubsub_channels는 채널을 저장하는 dict 구조체를 가리킨다.

dict구조체는 여러개의 dictEntry를 가지고 있고 각 Entry는 key : robj channel, value : list 타입인데 list는 linked list로 이루어져있다.

각각의 노드에는 그 채널에 맞는 client들이 존재한다.

publish 동작 과정
PUBLISH channel_name message → dict(hash table)에서 channel을 찾고 그 value에 있는 linked list를 돌면서 client들에게 메세지를 보낸다.

server.pubsub_patterns → server측에서 패턴을 저장하는 구조

pubsub_patterns는 패턴을 저장하는 노드의 리스트를 가리킨다. 각각 노드는 Pattern 구조체를 가리키고 이 구조체는 client와 pattern을 가진다.
즉 channel과 patterns는 따로 관리된다.

publish 동작 과정
PUBLISH channel_name message → 일단 pubsub_channels에서 처럼 channel 명으로 클라이언트에 메시지를 보낸 다음 pubsub_patterns 를 돌며 패턴에 맞는 client에게 메시지를 보낸다. (pattern을 보면 robj 라는 단어가 붙어있는데 저건 redis object라고 생각하면 된다. 참고로 redis는 C언어로 개발되어있다.)

client.pubsub_channels → client측에서 channel을 저장하는 구조

server측에 비해 빈약해보이지만 당연한게 subscribe를 하는 client측은 어차피 메세지가 오면 받기만 한다. channel들만 기억하면 끝

그래서 dictEntry까지는 server측과 똑같은데 한가지 다른점은 Entry가 channel만 가지고 value가 없다는점이다. (어떻게 보면 당연하다)

subscribe 동작 과정
subscribe channel_name -> server구조체와 client구조체 둘다 채널을 저장한다. client 구조체는 해당 채널을 등록하고 클라이언트를 pubsub 모드로 전환하는 역할을 한다. 평상시 (unsubsribe) 에는 normal 모드이고 client buffer가 무제한이다. 그러나 subscribe를 하게 되면 pubsub모드로 변하게 되면서 hard limit : 32mb, soft limit: 8mb로 제한한다.

  • subscribe : pubsob mode (16 ~ 32 mb)
  • unsubscribe : normal mode ( ∞ )

client.pubsub_patterns → client측에서 pattern을 저장하는 구조

client가 pattern을 저장하는 구조체이다. server.pubsub_patterns는 client와 pattern을 묶어서 저장해야했기 때문에 pubsubPattern이라는 robj을 하나 더 가졌지만 여기서는 패턴만 기억하면 되기 때문에 그냥 노드에 direct로 robj pattern이 연결되어있다.

psubscribe 동작 과정
psubscribe pattern > list에 pattern을 저장하고 서버 구조체에도 패턴을 저장하며 클라이언트를 pubsub모드로 전환하는 역할도 한다.

outro

오늘은 정말 재미없는 redis pubsub의 구조체 형태를 알아봤다. 정말 다시봐도 재미없고 보기 싫지만 이렇게 한번쯤 알아두고나면 pubsub의 내부동작에 대해 조금은 이해할 수 있다. 이러한 내부 구조체까지 전부 알필요는 없지만 이러한 형태를 이해하고 나면 나중에 streams 데이터구조로 publish subscriber를 구현하는 부분이 있는데 이걸 모르고 보면 정말 답도없다 거기는.. 다음 포스트에서는 실제 python에서 어떻게 활용할 수 있을지 FastAPI 위에서 redis를 다뤄보겠다.

reference
Redis pubsub
redisgate

profile
코딩하는 너구리

2개의 댓글

comment-user-thumbnail
2022년 9월 29일

kafka는 영속성을 가지고 메시지를 저장하기 때문에 편지가 사라진다라는 표현은 잘못된 표현일 수도 있을 것 같아요

1개의 답글