[입 개발] Redis Internals : PubSub

오늘은 Redis 를 사용하는 큰 이유중에 하나인 Publish/Subscribe 에 대해서 알아보려고 합니다.

먼저 Redis에서 사용하는 pubsub 관련 command 들은 다음과 같은 것들이 있습니다.

  • subscribe
  • unsubscribe
  • psubscribe
  • punsubscribe
  • publish

 

사실 publish 관련 명령은 하나 뿐이니 간단하게 이해할 수 있을 것이고,  앞에 p가 붙느냐 아니냐는 이게 pattern 으로 등록을 하느냐, 정확한 채널명인가를 지정하게 됩니다. 예를 들어서 subscribe 는 그냥 채널명과 일치하는 것의 데이터를 가져오게 되고, 앞에 p가 붙는 psubscribe 는 패턴을 등록해서 그 패턴과 일치하면 데이터를 전달하게 됩니다.

 

pub/sub의 구현을 위해서 redisServer 구조체 안에는 다음과 같은 구조체가 2개가 있습니다. key변경에 대한 notification 부분이 추가되었는데, 이 부분은 그냥  pub/sub을 이용하기 때문에 일단 생략하도록 하겠습니다.

 


/* Pubsub */
 dict *pubsub_channels; /* Map channels to list of subscribed clients */
 list *pubsub_patterns; /* A list of pubsub_patterns */

 

보면 채널은 dict 고 pattern은 list인걸 알 수 있습니다. 이건 channel은 같은 이름을 hash로만 찾으면 되는데, pattern은 해당 pattern이 모두 일치하는 지를 확인해야 하기 때문에 그렇습니다. 즉 여기서 pattern을 많이 사용하면, 느려질꺼라는 것을 예상할 수 있습니다.

 

subscribe 와 psubscribe에서 등록 과정은 각각 dict 에 넣거나 list에 넣는 것이 전부라 크게 볼 부분이 없고, publish 부분을 살펴보도록 하겠습니다. publish 과정은 두 단계로 이루어집니다.

  1. channel 에 있는 것중에 일치하는 것을 dictFind 로 찾아서 있으면 해당 등록된 클라이언트에게 모두 전송
  2. pattern 에 일치하는 것을 찾아서 해당 등록된 클라이언트에게 모두 전송

코드는 간단합니다. 다음과 같습니다.


/* Publish a message */
int pubsubPublishMessage(robj *channel, robj *message) {
 int receivers = 0;
 struct dictEntry *de;
 listNode *ln;
 listIter li;

/* Send to clients listening for that channel */
 de = dictFind(server.pubsub_channels,channel);
 if (de) {
 list *list = dictGetVal(de);
 listNode *ln;
 listIter li;

listRewind(list,&li);

while ((ln = listNext(&li)) != NULL) {
 redisClient *c = ln->value;

addReply(c,shared.mbulkhdr[3]);
 addReply(c,shared.messagebulk);
 addReplyBulk(c,channel);
 addReplyBulk(c,message);
 receivers++;
 }
 }
 /* Send to clients listening to matching channels */

if (listLength(server.pubsub_patterns)) {
 listRewind(server.pubsub_patterns,&li);
 channel = getDecodedObject(channel);
 while ((ln = listNext(&li)) != NULL) {
 pubsubPattern *pat = ln->value;

if (stringmatchlen((char*)pat->pattern->ptr,
 sdslen(pat->pattern->ptr),
 (char*)channel->ptr,
 sdslen(channel->ptr),0)) {
 addReply(pat->client,shared.mbulkhdr[4]);
 addReply(pat->client,shared.pmessagebulk);
 addReplyBulk(pat->client,pat->pattern);
 addReplyBulk(pat->client,channel);
 addReplyBulk(pat->client,message);
 receivers++;
 }
 }
 decrRefCount(channel);
 }
 return receivers;
}

 

위의 소스 코드를 보면 실제 pubsub_channels 과 pubsub_patterns 가 전부 내부에 리스트로 클라이언트를 관리하고 있는 것을 알 수 있습니다.  Redis 안에서 pub/sub은 굉장히 쉽게 구현되어 있습니다.