Redis-发布与订阅

来源:互联网 发布:使用vmware安装linux 编辑:程序博客网 时间:2024/04/29 10:04

订阅模式

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
原创粉丝点击