Redis-发布与订阅

订阅模式

Redis 提供两个订阅模式:频道(channel)订阅和 glob-style 模式(pattern)频道订阅。

相关命令模式:

  1. 频道订阅:SUBSCRIBE [channel-1 …channel-N ]
  2. 频道退订:UNSUBSCRIBE [channel-1 …channel-N ]
  3. 查看服务器当前被订阅的频道:PUBSUB CHANNELS [pattern]
  4. 查看频道的订阅数量:PUBSUB NUMSUB [channel-1 … channel-N]
  5. 模式订阅:PSUBSCRIBE pattern
  6. 模式退订:PUNSUBSCRIBE pattern
  7. 查看模式的订阅数量:PUBSUB NUMPAT
  8. 发送消息:PUBLISH channel message

在Redis中的存放形式

struct redisServer
{
   /* Pubsub */
   // 字典,键为频道,值为链表
   // 链表中保存了所有订阅某个频道的客户端
   // 新客户端总是被添加到链表的表尾
   dict *pubsub_channels;  //存放在哈希表中

   // 这个链表记录了客户端订阅的所有模式的名字
   list *pubsub_patterns; //存放在链表中 
}

频道的订阅与退订

Redis将所有频道的订阅关系都保存在服务器中的pubsub_channels里边,它的键是被订阅的频道,值是一个链表,链表里面记录了所有订阅这个频道的Client。
这里写图片描述

订阅频道SUBSCRIBE

当Client订阅频道时,服务器会将Client与被订阅的频道在pubsub_channels 中进行关联。具体分为下边两种情况进行关联:

  • 频道已经存在,则pubsub_channels中必然有相应的订阅者链表,将Client添加到订阅者链表的末尾。
  • 如果频道不存在,首先在pubsub_channels字典中创建该频道,再将Client添加到链表末尾。

源代码接口: ../src/pubsubs.c

int pubsubSubscribeChannel(redisClient *c, robj *channel) 
{
dictEntry *de;
list *clients = NULL;
int retval = 0;
// 将 channels 填接到 c->pubsub_channels 的集合中(值为 NULL 的字典视为集合),这里是看看Client之前是否已经订阅过这个频道了,(值为NULL是因为client自己也要像Server那样存一份自己的频道数据,而频道对应的Client就是自己本身,所以就设为NULL)
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) 
{
    retval = 1;
    //将自己的引用计数加一
    incrRefCount(channel);
    // 从 pubsub_channels 字典中取出保存着所有订阅了 channel 的客户端的链表
    // 如果 channel 不存在于字典,那么添加进去
    //查找下Server的pubsub_channels中是否有此频道,有返回频道在字典中的位置,没有返回NULL
    de = dictFind(server.pubsub_channels,channel);
    if (de == NULL) 
    {
    //Server中没有此频道信息,先创建一个空链表,将空链表和频道信息加多Server频道字典中
        clients = listCreate();
        dictAdd(server.pubsub_channels,channel,clients);
        incrRefCount(channel);
    }
    else 
    {
    //Server中有此频道信息,那么就获取该频道的链表clients
        clients = dictGetVal(de);
    }
    // 将客户端添加到链表的末尾
    listAddNodeTail(clients,c);
}
/* Notify the client */
// 回复客户端。
addReply(c,shared.mbulkhdr[3]);
// "subscribe\n" 字符串
addReply(c,shared.subscribebulk);
// 被订阅的客户端
addReplyBulk(c,channel);
// 客户端订阅的频道数量(pubsub_channels)和模式总数(pubsub_patterns)
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
}
退订频道UNSUBSCRIBE

收到退订消息后,服务器将从pubsub_channels中解除Client与频道之间的关联:

  • 根据退订频道的名字,在pubsub_channels中找到频道对应的订阅者链表,然后将退订Client的信息由链表中删除。
  • 如果删除退订Client后,频道的订阅者链表变空,则将频道由pubsub_channels字典中删除。

源代码接口: ../src/pubsubs.c

int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
dictEntry *de;
list *clients;
listNode *ln;
int retval = 0;

// 将频道 channel 从 client->channels 字典中移除
incrRefCount(channel); 

if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
    // channel 移除成功,表示客户端订阅了这个频道,执行以下代码
    retval = 1;
    // 从 channel->clients 的 clients 链表中,移除 client 
    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);
    // 如果移除 client 之后链表为空,那么删除这个 channel 键
    if (listLength(clients) == 0) {
        dictDelete(server.pubsub_channels,channel);
    }
}
/* Notify the client */
// 回复客户端
if (notify) {
    addReply(c,shared.mbulkhdr[3]);
    // "ubsubscribe" 字符串
    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;
}

模式订阅与退订

Redis服务器将模式的订阅关系保存到pubsub_patterns中,pubsub_patterns是一个链表,链表的每个节点都包含一个pubsub_Pattern结构。

struct pubsubPattern{      
   //订阅模式的客户端     
       redisClient *client;                             
   // 被订阅的模式
   robj *pattern; 
}          

Pubsub_pattern结构示意图

订阅模式 psubscribe

服务器对每个被订阅的模式执行以下两个操作:
1、新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。
2、将pubsubPattern结构体添加到pubsub_patterns链表的表尾。

源码接口:../src/pubsub.c

int pubsubSubscribePattern(redisClient *c, robj *pattern) 
{
int retval = 0;

// 在链表中查找模式,看客户端是否已经订阅了这个模式
// 这里为什么不像 channel 那样,用字典来进行检测呢?
// 虽然 pattern 的数量一般来说并不多
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {

    // 如果没有的话,执行以下代码

    retval = 1;

    pubsubPattern *pat;

    // 将 pattern 添加到 c->pubsub_patterns 链表中
    listAddNodeTail(c->pubsub_patterns,pattern);

    incrRefCount(pattern);

    // 创建并设置新的 pubsubPattern 结构
    pat = zmalloc(sizeof(*pat));
    pat->pattern = getDecodedObject(pattern);
    pat->client = c;

    // 添加到末尾
    listAddNodeTail(server.pubsub_patterns,pat);
}

/* Notify the client */
// 回复客户端。
addReply(c,shared.mbulkhdr[3]);
// 回复 "psubscribe" 字符串
addReply(c,shared.psubscribebulk);
// 回复被订阅的模式
addReplyBulk(c,pattern);
// 回复客户端订阅的频道和模式的总数
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
 }
退订模式 punsubscribe

服务器收到Client退订某个或多个模式时,在链表pubsub_patterns中查找并删除那些pattern属性为被退订模式,并且Client属性为执行退订命令的客户端的pubsubPattern结构。

源码接口:../src/pubsub.c

int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
listNode *ln;
pubsubPattern pat;
int retval = 0;

incrRefCount(pattern); 

// 先确认一下,客户端是否订阅了这个模式
if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {

    retval = 1;

    // 将模式从客户端的订阅列表中删除
    listDelNode(c->pubsub_patterns,ln);

    // 设置 pubsubPattern 结构
    pat.client = c;
    pat.pattern = pattern;

    // 在服务器中查找
    ln = listSearchKey(server.pubsub_patterns,&pat);
    listDelNode(server.pubsub_patterns,ln);
}

/* Notify the client */
// 回复客户端
if (notify) {
    addReply(c,shared.mbulkhdr[3]);
    // "punsubscribe" 字符串
    addReply(c,shared.punsubscribebulk);
    // 被退订的模式
    addReplyBulk(c,pattern);
    // 退订频道之后客户端仍在订阅的频道和模式的总数
    addReplyLongLong(c,dictSize(c->pubsub_channels)+
                   listLength(c->pubsub_patterns));
}

decrRefCount(pattern);

return retval;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值