相關(guān)命令:
PUBLISH? ? ? ? ? ? 發(fā)布
SUBSCRIBE? ? ? ? ? 訂閱
PSUBSCRIBE? ? ? ? ? 一種訂閱符合給定模式的所有頻道的方法
UNSUBSCRIBE? ? ? ? 退訂
PUNSUBSCRIBE? ? ? ? 退訂一個訂閱的模式
這些命令被廣泛用于構(gòu)建即時通信應(yīng)用,比如網(wǎng)絡(luò)聊天室(chatroom)和實時廣播燎窘、實時提醒等弧岳。
Redis相關(guān)源碼文件:pubsub.c
使用
PUBLISH 命令用于向給定的頻道發(fā)送信息轻局,返回值為接收到信息的訂閱者數(shù)量
redis> PUBLISH treehole "top secret here ..."
(integer) 0
redis> PUBLISH chatroom "hi?"
(integer) 1
SUBSCRIBE 命令訂閱給定的一個或多個頻道:
? redis> SUBSCRIBE chatroom
Reading messages... (press Ctrl-C to quit)
1) "subscribe" # 訂閱反饋
2) "chatroom" # 訂閱的頻道
3) (integer) 1 # 目前客戶端已訂閱頻道/模式的數(shù)量
1) "message" # 信息
2) "chatroom" # 發(fā)送信息的頻道
3) "hi?" # 信息內(nèi)容
SUBSCRIBE 的返回值當中诲祸, 1) ""subscribe""是訂閱的反饋信息歉铝,1)"message "的則是訂閱的頻道所發(fā)送的信息。
SUBSCRIBE 還可以訂閱多個頻道谍婉,這樣一來它接收到的信息就可能來自多個頻道:
redis> SUBSCRIBE chatroom talk-to-jack
Reading messages... (press Ctrl-C to quit)
1) "subscribe" # 訂閱 chatroom 的反饋
2) "chatroom"
3) (integer) 1
1) "subscribe" # 訂閱 talk-to-jack 的反饋
2) "talk-to-jack"
3) (integer) 2
1) "message" # 來自 chatroom 的消息
2) "chatroom"
3) "yahoo"
1) "message" # 來自 talk-to-peter 的消息
2) "talk-to-jack"
3) "Goodmorning, peter."
PSUBSCRIBE 提供了一種訂閱符合給定模式的所有頻道的方法更啄,比如說稚疹,使用 it.* 為輸入,就可以訂閱所有以 it. 開頭的頻道祭务,比如 it.news 内狗、 it.blog 、 it.tweets 义锥,諸如此類:
redis> PSUBSCRIBE it.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "it.*"
3) (integer) 1
1) "pmessage"
2) "it.*" # 匹配的模式
3) "it.news" # 消息的來源頻道
4) "Redis 2.6rc5 release" # 消息內(nèi)容
1) "pmessage"
2) "it.*"
3) "it.blog"
4) "Why NoSQL matters"
1) "pmessage"
2) "it.*"
3) "it.tweet"
4) "@redis: when will the 2.6 stable release?"
當然柳沙, PSUBSCRIBE 也可以接受多個參數(shù),從而匹配多種模式拌倍。
UNSUBSCRIBE 和 PUNSUBSCRIBE 負責退訂給定的頻道或模式赂鲤。
內(nèi)部實現(xiàn)
流程
當一個客戶端通過 PUBLISH 命令向訂閱者發(fā)送信息的時候,我們稱這個客戶端為發(fā)布者(publisher)柱恤。
而當一個客戶端使用 SUBSCRIBE 或者 PSUBSCRIBE 命令接收信息的時候数初,我們稱這個客戶端為訂閱者(subscriber)。
為了解耦發(fā)布者(publisher)和訂閱者(subscriber)之間的關(guān)系梗顺,Redis 使用了 channel (頻道)作為兩者的中介 —— 發(fā)布者將信息直接發(fā)布給 channel 泡孩,而 channel 負責將信息發(fā)送給適當?shù)挠嗛喺撸l(fā)布者和訂閱者之間沒有相互關(guān)系寺谤,也不知道對方的存在
具體實現(xiàn)
SUBSCRIBE 命令的實現(xiàn)
Redis 將所有接受和發(fā)送信息的任務(wù)交給 channel 來進行珍德,而所有 channel 的信息就儲存在 redisServer 這個結(jié)構(gòu)中:
struct redisServer {
// ......
dict *pubsub_channels;? ? // Map channels to list of subscribed clients
// ......
};
pubsub_channels 是一個字典,字典的鍵就是一個個 channel 矗漾,而字典的值則是一個鏈表,鏈表中保存了所有訂閱這個 channel 的客戶端薄料。(haspmap之類)
實現(xiàn) SUBSCRIBE 命令的關(guān)鍵敞贡,就是將客戶端添加到給定 channel 的訂閱鏈表中。
函數(shù) pubsubSubscribeChannel 是 SUBSCRIBE 命令的底層實現(xiàn)摄职,它完成了將客戶端添加到訂閱鏈表中的工作:
? ? // 訂閱指定頻道
// 訂閱成功返回 1 誊役,如果已經(jīng)訂閱過获列,返回 0
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
struct dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
// dictAdd 在添加新元素成功時返回 DICT_OK
// 因此這個判斷句表示,如果新訂閱 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 */
// 將 client 添加到訂閱給定 channel 的鏈表中
// 這個鏈表是一個哈希表的值巩梢,哈希表的鍵是給定 channel
// 這個哈希表保存在 server.pubsub_channels 里
de = dictFind(server.pubsub_channels,channel);
if (de == NULL) {
// 如果 de 等于 NULL
// 表示這個客戶端是首個訂閱這個 channel 的客戶端
// 那么創(chuàng)建一個新的列表, 并將它加入到哈希表中
clients = listCreate();
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
// 如果 de 不為空艺玲,就取出這個 clients 鏈表
clients = dictGetVal(de);
}
// 將客戶端加入到鏈表中
listAddNodeTail(clients,c);
}
/* Notify the client */
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.subscribebulk);
// 返回訂閱的頻道
addReplyBulk(c,channel);
// 返回客戶端當前已訂閱的頻道和模式數(shù)量的總和
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
}
PSUBSCRIBE 命令的實現(xiàn)
和 redisServer.pubsub_channels 屬性類似括蝠, redisServer.pubsub_patterns 屬性用于保存所有被訂閱的模式,和 pubsub_channels 不同的是饭聚, pubsub_patterns 是一個鏈表(而不是字典):
struct redisServer {
// ......
list *pubsub_patterns; // A list of pubsub_patterns
// ......
};
“我自己是一名Java架構(gòu)師忌警,辭職目前在做講師,整理了一份學(xué)習Java干貨秒梳,無論是剛需的高級面試專題還是常用的數(shù)據(jù)算法都有整理法绵,送給每一位Java小伙伴。在日新月異的程序世界里酪碘,我們每一個人都是學(xué)生朋譬。"
加群:712477306 (招募中)