Redis原始碼分析(三十)--- pubsub釋出訂閱模式
Redis原始碼分析(三十)--- pubsub釋出訂閱模式
今天學習了Redis中比較高大上的名詞,“釋出訂閱模式”,釋出訂閱模式這個詞在我最開始接觸聽說的時候是在JMS(Java Message Service)java訊息服務中聽說的。這個名次用通俗的一點話說,就是我訂閱了這類訊息,當只有這類的訊息進行廣播傳送的時候,我才會,其他的訊息直接過濾,保證了一個高效的傳輸效率。下面切入正題,學習一下Redis是如何實現這個釋出訂閱模式的。先看看裡面的簡單的API構造;
/*-----------------------------------------------------------------------------
* Pubsub low level API
*----------------------------------------------------------------------------*/
void freePubsubPattern(void *p) /* 釋放釋出訂閱的模式 */
int listMatchPubsubPattern(void *a, void *b) /* 釋出訂閱模式是否匹配 */
int clientSubscriptionsCount(redisClient *c) /* 返回客戶端的所訂閱的數量,包括channels + patterns管道和模式 */
int pubsubSubscribeChannel(redisClient *c, robj *channel) /* Client訂閱一個Channel管道 */
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) /* 取消訂閱Client中的Channel */
int pubsubSubscribePattern(redisClient *c, robj *pattern) /* Client客戶端訂閱一種模式 */
int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) /* Client客戶端取消訂閱pattern模式 */
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) /* 客戶端取消自身訂閱的所有Channel */
int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) /* 客戶端取消訂閱所有的pattern模式 */
int pubsubPublishMessage(robj *channel, robj *message) /* 為所有訂閱了Channel的Client傳送訊息message */
/* ------------PUB/SUB API ---------------- */
void subscribeCommand(redisClient *c) /* 訂閱Channel的命令 */
void unsubscribeCommand(redisClient *c) /* 取消訂閱Channel的命令 */
void psubscribeCommand(redisClient *c) /* 訂閱模式命令 */
void punsubscribeCommand(redisClient *c) /* 取消訂閱模式命令 */
void publishCommand(redisClient *c) /* 釋出訊息命令 */
void pubsubCommand(redisClient *c) /* 釋出訂閱命令 */
在這裡面出現了高頻的詞Pattern(模式)和Channel(頻道,叫管道比較彆扭),也就是說,後續所有的關於釋出訂閱的東東都是基於這2者展開進行的。現在大致講解一下在Redis中是如何實現此中模式的:1.在RedisClient 內部維護了一個pubsub_channels的Channel列表,記錄了此客戶端所訂閱的頻道
2.在Server服務端,同樣維護著一個類似的變數叫做,pubsub_channels,這是一個dict字典變數,每一個Channel對應著一批訂閱了此頻道的Client,也就是Channel-->list of Clients
3.當一個Client publish一個message的時候,會先去服務端的pubsub_channels找相應的Channel,遍歷裡面的Client,然後傳送通知,即完成了整個釋出訂閱模式。
我們可以簡單的看一下Redis訂閱一個Channel的方法實現;
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
* 0 if the client was already subscribed to that channel. */
/* Client訂閱一個Channel管道 */
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
struct dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
//在Client的字典pubsub_channels中新增Channel
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
/* Add the client to the channel -> list of clients hash table */
//新增Clietn到server中的pubsub_channels,對應的列表中
de = dictFind(server.pubsub_channels,channel);
if (de == NULL) {
//如果此頻道的Client列表為空,則建立新列表並新增
clients = listCreate();
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
//否則,獲取這個頻道的客戶端列表,在尾部新增新的客戶端
clients = dictGetVal(de);
}
listAddNodeTail(clients,c);
}
/* Notify the client */
//新增給回覆客戶端
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.subscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,clientSubscriptionsCount(c));
return retval;
}
新增操作主要分2部,Client自身的內部維護的pubsub_channels的新增,是一個dict字典物件,然後,是server端維護的pubsub_channels中的client列表的新增。在進行Channel頻道的刪除的時候,也是執行的這2步驟操作:/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
* 0 if the client was not subscribed to the specified channel. */
/* 取消訂閱Client中的Channel */
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
struct dictEntry *de;
list *clients;
listNode *ln;
int retval = 0;
/* Remove the channel from the client -> channels hash table */
incrRefCount(channel); /* channel may be just a pointer to the same object
we have in the hash tables. Protect it... */
//字典刪除Client中pubsub_channels中的Channel
if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
retval = 1;
/* Remove the client from the channel -> clients list hash table */
//再移除Channel對應的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);
if (listLength(clients) == 0) {
/* Free the list and associated hash entry at all if this was
* the latest client, so that it will be possible to abuse
* Redis PUBSUB creating millions 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;
}
裡面還有對應的模式的訂閱和取消訂閱的操作,原理和channel完全一致,二者的區別在於,pattern是用來匹配的Channel的,這個是什麼意思呢。在後面會做出答案,接著看。最後看一個最最核心的方法,客戶端發步訊息方法:/* Publish a message */
/* 為所有訂閱了Channel的Client傳送訊息message */
int pubsubPublishMessage(robj *channel, robj *message) {
int receivers = 0;
struct dictEntry *de;
listNode *ln;
listIter li;
/* Send to clients listening for that channel */
//找到Channel所對應的dictEntry
de = dictFind(server.pubsub_channels,channel);
if (de) {
//獲取此Channel對應的客戶單列表
list *list = dictGetVal(de);
listNode *ln;
listIter li;
listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
//依次取出List中的客戶單,新增訊息回覆
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 */
/* 傳送給嘗試匹配該Channel的客戶端訊息 */
if (listLength(server.pubsub_patterns)) {
listRewind(server.pubsub_patterns,&li);
channel = getDecodedObject(channel);
while ((ln = listNext(&li)) != NULL) {
pubsubPattern *pat = ln->value;
//客戶端的模式如果匹配了Channel,也會傳送訊息
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;
}
pattern的作用就在上面體現了,如果某種pattern匹配了Channel頻道,則模式的客戶端也會接收訊息。在server->pubsub_patterns中,pubsub_patterns是一個list列表,裡面的每一個pattern只對應一個Client,就是上面的pat->client,這一點和Channel還是有本質的區別的。講完釋出訂閱模式的基本操作後,順便把與此相關的notify通知類也稍稍講講,通知只有3個方法,/* ----------------- API ------------------- */
int keyspaceEventsStringToFlags(char *classes) /* 鍵值字元型別轉為對應的Class型別 */
sds keyspaceEventsFlagsToString(int flags) /* 通過輸入的flag值類,轉為字元型別*/
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) /* 釋出通知方法,分為2類,keySpace的通知,keyEvent的通知 */
涉及到string To flag 和flag To String 的轉換,也不知道這個會在哪裡用到;/* Turn a string representing notification classes into an integer
* representing notification classes flags xored.
*
* The function returns -1 if the input contains characters not mapping to
* any class. */
/* 鍵值字元型別轉為對應的Class型別 */
int keyspaceEventsStringToFlags(char *classes) {
char *p = classes;
int c, flags = 0;
while((c = *p++) != '\0') {
switch(c) {
case 'A': flags |= REDIS_NOTIFY_ALL; break;
case 'g': flags |= REDIS_NOTIFY_GENERIC; break;
case '$': flags |= REDIS_NOTIFY_STRING; break;
case 'l': flags |= REDIS_NOTIFY_LIST; break;
case 's': flags |= REDIS_NOTIFY_SET; break;
case 'h': flags |= REDIS_NOTIFY_HASH; break;
case 'z': flags |= REDIS_NOTIFY_ZSET; break;
case 'x': flags |= REDIS_NOTIFY_EXPIRED; break;
case 'e': flags |= REDIS_NOTIFY_EVICTED; break;
case 'K': flags |= REDIS_NOTIFY_KEYSPACE; break;
case 'E': flags |= REDIS_NOTIFY_KEYEVENT; break;
default: return -1;
}
}
return flags;
}
應該是響應鍵盤輸入的型別和Redis型別之間的轉換。在notify的方法還有一個event事件的通知方法:/* The API provided to the rest of the Redis core is a simple function:
*
* notifyKeyspaceEvent(char *event, robj *key, int dbid);
*
* 'event' is a C string representing the event name.
* 'key' is a Redis object representing the key name.
* 'dbid' is the database ID where the key lives. */
/* 釋出通知方法,分為2類,keySpace的通知,keyEvent的通知 */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
sds chan;
robj *chanobj, *eventobj;
int len = -1;
char buf[24];
/* If notifications for this class of events are off, return ASAP. */
if (!(server.notify_keyspace_events & type)) return;
eventobj = createStringObject(event,strlen(event));
//2種的通知形式,略有差別
/* __keyspace@<db>__:<key> <event> notifications. */
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYSPACE) {
chan = sdsnewlen("__keyspace@",11);
len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, key->ptr);
chanobj = createObject(REDIS_STRING, chan);
//上述幾步操作,元件格式字串,最後釋出訊息,下面keyEvent的通知同理
pubsubPublishMessage(chanobj, eventobj);
decrRefCount(chanobj);
}
/* __keyevente@<db>__:<event> <key> notifications. */
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYEVENT) {
chan = sdsnewlen("__keyevent@",11);
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, eventobj->ptr);
chanobj = createObject(REDIS_STRING, chan);
pubsubPublishMessage(chanobj, key);
decrRefCount(chanobj);
}
decrRefCount(eventobj);
}
有keySpace和keyEvent的2種事件通知。具體怎麼用,等後面碰到的時候在看看。相關文章
- redis原始碼分析之釋出訂閱(pub/sub)Redis原始碼
- 設計模式之釋出訂閱模式(2) Redis 釋出/訂閱模式設計模式Redis
- redis 釋出與訂閱原理分析Redis
- SpringBoot Redis 釋出訂閱模式 Pub/SubSpring BootRedis模式
- Redis釋出訂閱Redis
- 結合 Vue 原始碼談談釋出-訂閱模式Vue原始碼模式
- Spring原始碼之七registerListeners()及釋出訂閱模式Spring原始碼模式
- 釋出訂閱模式模式
- 設計模式之釋出訂閱模式(5) Spring Events原始碼解析設計模式Spring原始碼
- JS訂閱釋出模式JS模式
- 設計模式之釋出訂閱模式(1) 一文搞懂釋出訂閱模式設計模式
- Laravel Redis釋出與訂閱.LaravelRedis
- Redis 的訂閱與釋出Redis
- Javascript(七)釋出-訂閱模式JavaScript模式
- 釋出訂閱模式學習模式
- 瑞士軍刀redis - 釋出訂閱Redis
- Redis系列(八):釋出與訂閱Redis
- Redis基礎系列-0x008:釋出訂閱模式Redis模式
- js設計模式--釋出訂閱模式JS設計模式
- 觀察者模式-訂閱釋出模式模式
- 從釋出訂閱模式入手讀懂Node.js的EventEmitter原始碼模式Node.jsMIT原始碼
- Spring 中的釋出-訂閱模式Spring模式
- 行為型:釋出訂閱模式模式
- MQTT 釋出/訂閱模式介紹MQQT模式
- 觀察者模式 vs 釋出訂閱模式模式
- JS設計模式七:釋出-訂閱模式JS設計模式
- js 觀察者模式 訂閱釋出模式JS模式
- JavaScript設計模式系列--釋出訂閱模式JavaScript設計模式
- Redis的訊息釋出和訂閱Redis
- 基於 Redis 的訂閱與釋出Redis
- Redis實現訊息釋出訂閱Redis
- 釋出訂閱 VS 觀察者模式模式
- 奇技淫巧之釋出訂閱模式模式
- JavaScript中釋出/訂閱模式的理解JavaScript模式
- 對釋出-訂閱者模式的解析模式
- js進階-設計模式: 釋出訂閱模式JS設計模式
- 觀察者模式和釋出訂閱模式(上)模式
- javascript設計模式 之 5 釋出-訂閱模式JavaScript設計模式