redis-2.6.16源码分析之pub-sub系统

  redis实现的发送订阅系统,即pub-sub,这部分的的代码比较少,也方便分析。在这只将会分析下普通的pub-sub(会忽略掉Pattern-matching subscriptions),以此来简述一个pubsub系统是如何实现的。

 在redis主要有介绍redis的pub-sub,在开始之前, 需要知道redis的pubsub的几个命令:

SUBSCRIBE first second //订阅两个channel,分别是first和second

PUBLISH secondHello   //发送方向channel是second的发送"hello"消息

UNSUBSCRIBE      //取消之前订阅的所有channel

 

  下面来看看在redis的Pubsub.c代码中对这些命令的实现。                       

首先看看subscribe的实现:

voidsubscribeCommand(redisClient *c) {
    int j;                             
 
    for (j = 1; j < c->argc; j++)
        //客户端订阅多个channel,对于需要订阅的每个channel都执行该函数
       pubsubSubscribeChannel(c,c->argv[j]);
}
 
 
/* Subscribe aclient to a channel. Returns 1 if the operation succeeded, or
 * 0 if the client was already subscribed tothat channel. */
intpubsubSubscribeChannel(redisClient *c, robj *channel) {
    struct dictEntry *de;
    list *clients = NULL;
    int retval = 0;
    
    //c->pubsub_channels是一个redis自己实现的hash表,这不是我们的重点,就不展开说了
    //dictAdd向hash表中增加一个key为channel,value为null的键值对
    /* Add the channel to the client ->channels hash table */
    if(dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        //从服务端查找当前需要订阅的channel是否已经在服务端登记过
        //没有登记过,则创建一个列表作为channel的值,并记录到服务端
        //也就是意味着服务端是通过这个列表来得知这个channel是被哪些客户端订阅过了
        //登记过,则取出在服务端,channel所对应的客户端列表
        /* Add the client to the channel ->list of clients hash table */
        de =dictFind(server.pubsub_channels,channel);
        if (de == NULL) {
            clients = listCreate();
           dictAdd(server.pubsub_channels,channel,clients);
            incrRefCount(channel);
        } else {
            clients = dictGetVal(de);
        }
        //向这个channel所对应的记录客户端的列表的尾部插入当前客户端,
        //这样服务端就当前客户端订阅了这个channel
        listAddNodeTail(clients,c);
    }
    /* Notify the client */
    addReply(c,shared.mbulkhdr[3]);
    addReply(c,shared.subscribebulk);
    addReplyBulk(c,channel);
    addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
    return retval;
}
 
//在看看pub的实现:
voidpublishCommand(redisClient *c) {
    //发送则向某个channel发送消息,如 PUBLISH second Hello 
        //c->args[1]就是channel second,第二个参数就是要发送的消息Hello
    int receivers =pubsubPublishMessage(c->argv[1],c->argv[2]);
    addReplyLongLong(c,receivers);
}
 
/*Publish a message */
intpubsubPublishMessage(robj *channel, robj *message) {
   int receivers = 0;
   struct dictEntry *de;
   listNode *ln;
   listIter li;
    
   //从服务端找到订阅了这个channel的客户端列表
   //遍历这个列表,将消息发送给每个客户端
   /* 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 */
   发送到模式匹配的订阅方的处理...
 
   return receivers;
}
 
//最后看看unsubscribe的处理:
void unsubscribeCommand(redisClient *c) {
    if (c->argc == 1) {
        //取消这个客户端的所有订阅
        pubsubUnsubscribeAllChannels(c,1);
    } else {
        int j;
 
        for (j = 1; j < c->argc; j++)
           //取消这个客户端的关于某个channel的订阅
            pubsubUnsubscribeChannel(c,c->argv[j],1);
    }
}
/* Unsubscribe from all the channels. Return the number of channels the
 * client was subscribed from. */
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
    //取得这个客户端的所有订阅的channel的迭代器
    dictIterator *di =dictGetSafeIterator(c->pubsub_channels);
    dictEntry *de;
    int count = 0;
    //通过遍历迭代器来获取在客户端记录的每个channel记录,并对每个记录取消订阅
    while((de = dictNext(di)) != NULL) {
        robj *channel = dictGetKey(de);
 
        count +=pubsubUnsubscribeChannel(c,channel,notify);
    }
    /* We were subscribed to nothing? Still reply to the client.*/
    if (notify && count == 0) {
        addReply(c,shared.mbulkhdr[3]);
        addReply(c,shared.unsubscribebulk);
        addReply(c,shared.nullbulk);
       addReplyLongLong(c,dictSize(c->pubsub_channels)+
                      listLength(c->pubsub_patterns));
    }
    dictReleaseIterator(di);
    return count;
}
 
* Unsubscribe a client from a channel. Returns 1 if the operationsucceeded, or
 * 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
    struct dictEntry *de;
    list *clients;
    listNode *ln;
    int retval = 0;
 
    //从客户端记录的hash表中删除这个channel记录,以此来取消客户对这个channel的订阅
    //从服务端查找channel所对应客户端列表,从这个列表中删除这个客户端的记录
    //这样就完成了取消订阅
    /* Remove the channel from the client -> channels hashtable */
    incrRefCount(channel); /* channel may be just a pointer tothe same object
                           we have in the hash tables. Protect it... */
    if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
        retval = 1;
        /* Remove the client from the channel ->clients list hash table */
        de = dictFind(server.pubsub_channels,channel);
        redisAssertWithInfo(c,NULL,de != NULL);
        clients = dictGetVal(de);
        ln = listSearchKey(clients,c);
        redisAssertWithInfo(c,NULL,ln != NULL);
        listDelNode(clients,ln);
        if (listLength(clients) == 0) {
            /* Free the list and associatedhash entry at all if this was
             * the latest client, sothat it will be possible to abuse
             * Redis PUBSUB creatingmillions of channels. */
           dictDelete(server.pubsub_channels,channel);
        }
    }
    /* Notify the client */
    if (notify) {
        addReply(c,shared.mbulkhdr[3]);
        addReply(c,shared.unsubscribebulk);
        addReplyBulk(c,channel);
       addReplyLongLong(c,dictSize(c->pubsub_channels)+
                      listLength(c->pubsub_patterns));
 
    }
    decrRefCount(channel); /* it is finally safe to release it*/
    return retval;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值