redis-訂閱與發(fā)布
Redis 通過 PUBLISH 、 SUBSCRIBE 等命令實(shí)現(xiàn)了訂閱與發(fā)布模式健芭, 這個(gè)功能提供兩種信息機(jī)制县钥, 分別是訂閱/發(fā)布到頻道和訂閱/發(fā)布到模式, 下文先討論訂閱/發(fā)布到頻道的實(shí)現(xiàn)慈迈, 再討論訂閱/發(fā)布到模式的實(shí)現(xiàn)若贮。
頻道的訂閱與信息發(fā)送
Redis 的 SUBSCRIBE 命令可以讓客戶端訂閱任意數(shù)量的頻道省有, 每當(dāng)有新信息發(fā)送到被訂閱的頻道時(shí), 信息就會(huì)被發(fā)送給所有訂閱指定頻道的客戶端谴麦。
作為例子蠢沿, 下圖展示了頻道 channel1 , 以及訂閱這個(gè)頻道的三個(gè)客戶端 —— client2 匾效、 client5 和 client1 之間的關(guān)系:
當(dāng)有新消息通過 PUBLISH 命令發(fā)送給頻道 channel1 時(shí)舷蟀,
這個(gè)消息就會(huì)被發(fā)送給訂閱它的三個(gè)客戶端:
在后面的內(nèi)容中, 我們將探討 SUBSCRIBE 和 PUBLISH 命令的實(shí)現(xiàn)面哼, 以及這套訂閱與發(fā)布機(jī)制的運(yùn)作原理野宜。
訂閱頻道
每個(gè) Redis 服務(wù)器進(jìn)程都維持著一個(gè)表示服務(wù)器狀態(tài)的 redis.h/redisServer 結(jié)構(gòu), 結(jié)構(gòu)的 pubsub_channels 屬性是一個(gè)字典魔策, 這個(gè)字典就用于保存訂閱頻道的信息:
struct redisServer {
// ...
dict *pubsub_channels;
// ...
};
其中匈子,字典的鍵為正在被訂閱的頻道, 而字典的值則是一個(gè)鏈表闯袒, 鏈表中保存了所有訂閱這個(gè)頻道的客戶端旬牲。
比如說,在下圖展示的這個(gè) pubsub_channels 示例中搁吓, client2 、 client5 和 client1 就訂閱了 channel1 吭历, 而其他頻道也分別被別的客戶端所訂閱:
當(dāng)客戶端調(diào)用 SUBSCRIBE 命令時(shí)堕仔, 程序就將客戶端和要訂閱的頻道在 pubsub_channels 字典中關(guān)聯(lián)起來。
舉個(gè)例子晌区,如果客戶端 client10086 執(zhí)行命令 SUBSCRIBE channel1 channel2 channel3 摩骨,那么前面展示的 pubsub_channels 將變成下面這個(gè)樣子:
SUBSCRIBE 命令的行為可以用偽代碼表示如下:
def SUBSCRIBE(client, channels):
# 遍歷所有輸入頻道
for channel in channels:
# 將客戶端添加到鏈表的末尾
redisServer.pubsub_channels[channel].append(client)
通過 pubsub_channels 字典, 程序只要檢查某個(gè)頻道是否為字典的鍵朗若, 就可以知道該頻道是否正在被客戶端訂閱恼五; 只要取出某個(gè)鍵的值, 就可以得到所有訂閱該頻道的客戶端的信息哭懈。
發(fā)送信息到頻道
了解了 pubsub_channels 字典的結(jié)構(gòu)之后灾馒, 解釋 PUBLISH 命令的實(shí)現(xiàn)就非常簡單了: 當(dāng)調(diào)用 PUBLISH channel message 命令, 程序首先根據(jù) channel 定位到字典的鍵遣总, 然后將信息發(fā)送給字典值鏈表中的所有客戶端睬罗。
比如說,對于以下這個(gè) pubsub_channels 實(shí)例旭斥, 如果某個(gè)客戶端執(zhí)行命令 PUBLISH channel1 "hello moto" 容达,那么 client2 、 client5 和 client1 三個(gè)客戶端都將接收到 "hello moto" 信息:
PUBLISH 命令的實(shí)現(xiàn)可以用以下偽代碼來描述:
def PUBLISH(channel, message):
# 遍歷所有訂閱頻道 channel 的客戶端
for client in server.pubsub_channels[channel]:
# 將信息發(fā)送給它們
send_message(client, message)
退訂頻道
使用 UNSUBSCRIBE 命令可以退訂指定的頻道垂券, 這個(gè)命令執(zhí)行的是訂閱的反操作: 它從 pubsub_channels 字典的給定頻道(鍵)中花盐, 刪除關(guān)于當(dāng)前客戶端的信息, 這樣被退訂頻道的信息就不會(huì)再發(fā)送給這個(gè)客戶端。
模式的訂閱與信息發(fā)送
當(dāng)使用 PUBLISH 命令發(fā)送信息到某個(gè)頻道時(shí)算芯, 不僅所有訂閱該頻道的客戶端會(huì)收到信息柒昏, 如果有某個(gè)/某些模式和這個(gè)頻道匹配的話, 那么所有訂閱這個(gè)/這些頻道的客戶端也同樣會(huì)收到信息也祠。
下圖展示了一個(gè)帶有頻道和模式的例子昙楚, 其中 tweet.shop.* 模式匹配了 tweet.shop.kindle 頻道和 tweet.shop.ipad 頻道, 并且有不同的客戶端分別訂閱它們?nèi)齻€(gè):
當(dāng)有信息發(fā)送到 tweet.shop.kindle 頻道時(shí)诈嘿, 信息除了發(fā)送給 clientX 和 clientY 之外堪旧, 還會(huì)發(fā)送給訂閱 tweet.shop.* 模式的 client123 和 client256 :
另一方面, 如果接收到信息的是頻道 tweet.shop.ipad 奖亚, 那么 client123 和 client256 同樣會(huì)收到信息:
訂閱模式
redisServer.pubsub_patterns 屬性是一個(gè)鏈表淳梦,鏈表中保存著所有和模式相關(guān)的信息:
struct redisServer {
// ...
list *pubsub_patterns;
// ...
};
鏈表中的每個(gè)節(jié)點(diǎn)都包含一個(gè) redis.h/pubsubPattern 結(jié)構(gòu):
typedef struct pubsubPattern {
redisClient *client;
robj *pattern;
} pubsubPattern;
client 屬性保存著訂閱模式的客戶端,而 pattern 屬性則保存著被訂閱的模式昔字。
每當(dāng)調(diào)用 PSUBSCRIBE 命令訂閱一個(gè)模式時(shí)爆袍, 程序就創(chuàng)建一個(gè)包含客戶端信息和被訂閱模式的 pubsubPattern 結(jié)構(gòu), 并將該結(jié)構(gòu)添加到 redisServer.pubsub_patterns 鏈表中作郭。
作為例子陨囊,下圖展示了一個(gè)包含兩個(gè)模式的 pubsub_patterns 鏈表, 其中 client123 和 client256 都正在訂閱 tweet.shop.* 模式:
如果這時(shí)客戶端 client10086 執(zhí)行 PSUBSCRIBE broadcast.list.* 夹攒, 那么 pubsub_patterns 鏈表將被更新成這樣:
通過遍歷整個(gè) pubsub_patterns 鏈表蜘醋,程序可以檢查所有正在被訂閱的模式,以及訂閱這些模式的客戶端咏尝。
發(fā)送信息到模式
發(fā)送信息到模式的工作也是由 PUBLISH 命令進(jìn)行的压语, 在前面講解頻道的時(shí)候, 我們給出了這樣一段偽代碼编检, 說它定義了 PUBLISH 命令的行為:
def PUBLISH(channel, message):
# 遍歷所有訂閱頻道 channel 的客戶端
for client in server.pubsub_channels[channel]:
# 將信息發(fā)送給它們
send_message(client, message)
但是胎食,這段偽代碼并沒有完整描述 PUBLISH 命令的行為, 因?yàn)?PUBLISH 除了將 message 發(fā)送到所有訂閱 channel 的客戶端之外允懂, 它還會(huì)將 channel 和 pubsub_patterns 中的模式進(jìn)行對比厕怜, 如果 channel 和某個(gè)模式匹配的話, 那么也將 message 發(fā)送到訂閱那個(gè)模式的客戶端蕾总。
完整描述 PUBLISH 功能的偽代碼定于如下:
def PUBLISH(channel, message):
# 遍歷所有訂閱頻道 channel 的客戶端
for client in server.pubsub_channels[channel]:
# 將信息發(fā)送給它們
send_message(client, message)
# 取出所有模式酣倾,以及訂閱模式的客戶端
for pattern, client in server.pubsub_patterns:
# 如果 channel 和模式匹配
if match(channel, pattern):
# 那么也將信息發(fā)給訂閱這個(gè)模式的客戶端
send_message(client, message)
舉個(gè)例子,如果 Redis 服務(wù)器的 pubsub_patterns 狀態(tài)如下:
那么當(dāng)某個(gè)客戶端發(fā)送信息 "Amazon Kindle, $69." 到 tweet.shop.kindle 頻道時(shí)谤专, 除了所有訂閱了 tweet.shop.kindle 頻道的客戶端會(huì)收到信息之外躁锡, 客戶端 client123 和 client256 也同樣會(huì)收到信息, 因?yàn)檫@兩個(gè)客戶端訂閱的 tweet.shop.* 模式和 tweet.shop.kindle 頻道匹配置侍。
退訂模式
使用 PUNSUBSCRIBE 命令可以退訂指定的模式映之, 這個(gè)命令執(zhí)行的是訂閱模式的反操作: 程序會(huì)刪除 redisServer.pubsub_patterns
鏈表中拦焚, 所有和被退訂模式相關(guān)聯(lián)的 pubsubPattern 結(jié)構(gòu), 這樣客戶端就不會(huì)再收到和模式相匹配的頻道發(fā)來的信息杠输。
小結(jié)
訂閱信息由服務(wù)器進(jìn)程維持的 redisServer.pubsub_channels
字典保存赎败,字典的鍵為被訂閱的頻道,字典的值為訂閱頻道的所有客戶端蠢甲。
當(dāng)有新消息發(fā)送到頻道時(shí)僵刮,程序遍歷頻道(鍵)所對應(yīng)的(值)所有客戶端,然后將消息發(fā)送到所有訂閱頻道的客戶端上鹦牛。
訂閱模式的信息由服務(wù)器進(jìn)程維持的 redisServer.pubsub_patterns
鏈表保存搞糕,鏈表的每個(gè)節(jié)點(diǎn)都保存著一個(gè) pubsubPattern
結(jié)構(gòu),結(jié)構(gòu)中保存著被訂閱的模式曼追,以及訂閱該模式的客戶端窍仰。程序通過遍歷鏈表來查找某個(gè)頻道是否和某個(gè)模式匹配。
當(dāng)有新消息發(fā)送到頻道時(shí)礼殊,除了訂閱頻道的客戶端會(huì)收到消息之外驹吮,所有訂閱了匹配頻道的模式的客戶端,也同樣會(huì)收到消息晶伦。
退訂頻道和退訂模式分別是訂閱頻道和訂閱模式的反操作碟狞。