[TOC]
1. 介紹
在軟件架構(gòu)中腊尚,發(fā)布-訂閱是一種消息范式,消息的發(fā)送者(稱為發(fā)布者)不會將消息直接發(fā)送給特定的接收者(稱為訂閱者)褐捻。而是將發(fā)布的消息分為不同的類別痪宰,無需了解哪些訂閱者(如果有的話)可能存在。同樣的匆光,訂閱者可以表達對一個或多個類別的興趣套像,只接收感興趣的消息,無需了解哪些發(fā)布者(如果有的話)存在终息。
https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85
發(fā)布/訂閱的拓撲:
2. redis 實現(xiàn)
在redis文檔你可以找到發(fā)布/訂閱
發(fā)布訂閱只有6條命令夺巩。
3. 命令
發(fā)布/訂閱的嗎,命令很少周崭,你可以在redis發(fā)布/訂閱命令查看
4. 訂閱
命令:==SUBSCRIBE channel [channel1 ...]==
命令說明:當前線程訂閱channel柳譬,如果有其他線程向channel發(fā)布消息,那么我們的線程就能收到發(fā)布的消息续镇。==阻塞線程==
返回值:訂閱的channel數(shù)量美澳,還有一些提示信息。
命令:==PSUBSCRIBE pattern [pattern1 ...]==
命令說明:當前線程訂閱指定模式的channel摸航,如果有其他線程向滿足模式的channel發(fā)布消息制跟,那么我們的線程就能收到發(fā)布的消息。==阻塞線程==
==在結(jié)合前面的例子中:訂閱了c:*的channel酱虎,在第一個用例中雨膨,存在c:test1,c:test2,此時c:*會收到2個channel的消息。==
返回值:訂閱的channel模式數(shù)量读串,還有一些提示信息聊记。
命令:==UNSUBSCRIBE channel [channel1 ...]==
命令說明:取消訂閱channel撒妈。
命令:==PUNSUBSCRIBE pattern [pattern1 ...]==
命令說明:取消訂閱channel模式。
5. 發(fā)布
命令:==PUBLISH channel message==
命令說明:發(fā)布message到channel.
返回值:實際訂閱的線程數(shù)量甥雕。(收到消息的數(shù)量)
6. 狀態(tài)
命令:==PUBSUB CHANNELS [pattern]==
命令說明:列出全部活躍的channel或者符合pattern的channel.
channel活躍指:至少有一個訂閱者踩身。
返回值:活躍channel名稱胀茵。
命令:==PUBSUB NUMSUB [channel1 ....]==
命令說明:獲取指定channel的訂閱個數(shù)社露。==如果不指定channel,返回空==
返回值:訂閱指定channel的數(shù)量(活躍的).==輸入channel模式為0==
命令:==PUBSUB NUMPAT==
命令說明:獲取channel模板個數(shù)琼娘。
返回值:獲取channel模板個數(shù)峭弟。
7. 作用域
發(fā)布/訂閱與key所在空間沒有關系,它不會受任何級別的干擾脱拼,包括不同數(shù)據(jù)庫編碼瞒瘸。 發(fā)布在db 10,訂閱可以在db 1。 如果你需要區(qū)分某些頻道熄浓,可以通過在頻道名稱前面加上所在環(huán)境的名稱(例如:測試環(huán)境情臭,演示環(huán)境,線上環(huán)境等)赌蔑。
8. 資料
頻道的訂閱與信息發(fā)送
Redis 的 SUBSCRIBE 命令可以讓客戶端訂閱任意數(shù)量的頻道俯在, 每當有新信息發(fā)送到被訂閱的頻道時, 信息就會被發(fā)送給所有訂閱指定頻道的客戶端娃惯。
作為例子跷乐, 下圖展示了頻道
channel1
, 以及訂閱這個頻道的三個客戶端 ——client2
趾浅、client5
和client1
之間的關系:[圖片上傳失敗...(image-76938a-1596192874374)]
當有新消息通過 PUBLISH 命令發(fā)送給頻道
channel1
時愕提, 這個消息就會被發(fā)送給訂閱它的三個客戶端:[圖片上傳失敗...(image-794f29-1596192874374)]
在后面的內(nèi)容中, 我們將探討 SUBSCRIBE 和 PUBLISH 命令的實現(xiàn)皿哨, 以及這套訂閱與發(fā)布機制的運作原理浅侨。
訂閱頻道
每個 Redis 服務器進程都維持著一個表示服務器狀態(tài)的
redis.h/redisServer
結(jié)構(gòu), 結(jié)構(gòu)的pubsub_channels
屬性是一個字典证膨, 這個字典就用于保存訂閱頻道的信息:struct redisServer { // ... dict *pubsub_channels; // ... };
其中如输,字典的鍵為正在被訂閱的頻道, 而字典的值則是一個鏈表椎例, 鏈表中保存了所有訂閱這個頻道的客戶端挨决。
比如說,在下圖展示的這個
pubsub_channels
示例中订歪,client2
脖祈、client5
和client1
就訂閱了channel1
, 而其他頻道也分別被別的客戶端所訂閱:[圖片上傳失敗...(image-7bbbc-1596192874374)]
當客戶端調(diào)用 SUBSCRIBE 命令時刷晋, 程序就將客戶端和要訂閱的頻道在
pubsub_channels
字典中關聯(lián)起來盖高。舉個例子慎陵,如果客戶端
client10086
執(zhí)行命令SUBSCRIBE channel1 channel2 channel3
,那么前面展示的pubsub_channels
將變成下面這個樣子:[圖片上傳失敗...(image-ceebff-1596192874374)]
SUBSCRIBE 命令的行為可以用偽代碼表示如下:
def SUBSCRIBE(client, channels): # 遍歷所有輸入頻道 for channel in channels: # 將客戶端添加到鏈表的末尾 redisServer.pubsub_channels[channel].append(client)
通過
pubsub_channels
字典喻奥, 程序只要檢查某個頻道是否為字典的鍵席纽, 就可以知道該頻道是否正在被客戶端訂閱; 只要取出某個鍵的值撞蚕, 就可以得到所有訂閱該頻道的客戶端的信息润梯。發(fā)送信息到頻道
了解了
pubsub_channels
字典的結(jié)構(gòu)之后, 解釋 PUBLISH 命令的實現(xiàn)就非常簡單了: 當調(diào)用PUBLISH channel message
命令甥厦, 程序首先根據(jù)channel
定位到字典的鍵纺铭, 然后將信息發(fā)送給字典值鏈表中的所有客戶端。比如說刀疙,對于以下這個
pubsub_channels
實例舶赔, 如果某個客戶端執(zhí)行命令PUBLISH channel1 "hello moto"
,那么client2
谦秧、client5
和client1
三個客戶端都將接收到"hello moto"
信息:[圖片上傳失敗...(image-1948ef-1596192874374)]
PUBLISH 命令的實現(xiàn)可以用以下偽代碼來描述:
def PUBLISH(channel, message): # 遍歷所有訂閱頻道 channel 的客戶端 for client in server.pubsub_channels[channel]: # 將信息發(fā)送給它們 send_message(client, message)
退訂頻道
使用 UNSUBSCRIBE 命令可以退訂指定的頻道竟纳, 這個命令執(zhí)行的是訂閱的反操作: 它從
pubsub_channels
字典的給定頻道(鍵)中, 刪除關于當前客戶端的信息疚鲤, 這樣被退訂頻道的信息就不會再發(fā)送給這個客戶端锥累。模式的訂閱與信息發(fā)送
當使用 PUBLISH 命令發(fā)送信息到某個頻道時, 不僅所有訂閱該頻道的客戶端會收到信息石咬, 如果有某個/某些模式和這個頻道匹配的話揩悄, 那么所有訂閱這個/這些頻道的客戶端也同樣會收到信息。
下圖展示了一個帶有頻道和模式的例子鬼悠, 其中
tweet.shop.*
模式匹配了tweet.shop.kindle
頻道和tweet.shop.ipad
頻道删性, 并且有不同的客戶端分別訂閱它們?nèi)齻€:[圖片上傳失敗...(image-e75ebe-1596192874374)]
當有信息發(fā)送到
tweet.shop.kindle
頻道時, 信息除了發(fā)送給clientX
和clientY
之外焕窝, 還會發(fā)送給訂閱tweet.shop.*
模式的client123
和client256
:[圖片上傳失敗...(image-9e6cc8-1596192874374)]
另一方面蹬挺, 如果接收到信息的是頻道
tweet.shop.ipad
, 那么client123
和client256
同樣會收到信息:[圖片上傳失敗...(image-ae1877-1596192874374)]
訂閱模式
redisServer.pubsub_patterns
屬性是一個鏈表它掂,鏈表中保存著所有和模式相關的信息:struct redisServer { // ... list *pubsub_patterns; // ... };
鏈表中的每個節(jié)點都包含一個
redis.h/pubsubPattern
結(jié)構(gòu):typedef struct pubsubPattern { redisClient *client; robj *pattern; } pubsubPattern;
client
屬性保存著訂閱模式的客戶端巴帮,而pattern
屬性則保存著被訂閱的模式。每當調(diào)用
PSUBSCRIBE
命令訂閱一個模式時虐秋, 程序就創(chuàng)建一個包含客戶端信息和被訂閱模式的pubsubPattern
結(jié)構(gòu)榕茧, 并將該結(jié)構(gòu)添加到redisServer.pubsub_patterns
鏈表中。作為例子客给,下圖展示了一個包含兩個模式的
pubsub_patterns
鏈表用押, 其中client123
和client256
都正在訂閱tweet.shop.*
模式:[圖片上傳失敗...(image-daa4ed-1596192874374)]
如果這時客戶端
client10086
執(zhí)行PSUBSCRIBE broadcast.list.*
, 那么pubsub_patterns
鏈表將被更新成這樣:[圖片上傳失敗...(image-4c438e-1596192874374)]
通過遍歷整個
pubsub_patterns
鏈表靶剑,程序可以檢查所有正在被訂閱的模式蜻拨,以及訂閱這些模式的客戶端池充。發(fā)送信息到模式
發(fā)送信息到模式的工作也是由 PUBLISH 命令進行的, 在前面講解頻道的時候缎讼, 我們給出了這樣一段偽代碼收夸, 說它定義了 PUBLISH 命令的行為:
def PUBLISH(channel, message): # 遍歷所有訂閱頻道 channel 的客戶端 for client in server.pubsub_channels[channel]: # 將信息發(fā)送給它們 send_message(client, message)
但是,這段偽代碼并沒有完整描述 PUBLISH 命令的行為血崭, 因為 PUBLISH 除了將
message
發(fā)送到所有訂閱channel
的客戶端之外卧惜, 它還會將channel
和pubsub_patterns
中的模式進行對比, 如果channel
和某個模式匹配的話功氨, 那么也將message
發(fā)送到訂閱那個模式的客戶端序苏。完整描述 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ā)給訂閱這個模式的客戶端 send_message(client, message)
舉個例子捷凄,如果 Redis 服務器的
pubsub_patterns
狀態(tài)如下:[圖片上傳失敗...(image-eccf4c-1596192874374)]
那么當某個客戶端發(fā)送信息
"Amazon Kindle, $69."
到tweet.shop.kindle
頻道時, 除了所有訂閱了tweet.shop.kindle
頻道的客戶端會收到信息之外围来, 客戶端client123
和client256
也同樣會收到信息跺涤, 因為這兩個客戶端訂閱的tweet.shop.*
模式和tweet.shop.kindle
頻道匹配。退訂模式
使用 PUNSUBSCRIBE 命令可以退訂指定的模式监透, 這個命令執(zhí)行的是訂閱模式的反操作: 程序會刪除
redisServer.pubsub_patterns
鏈表中桶错, 所有和被退訂模式相關聯(lián)的pubsubPattern
結(jié)構(gòu), 這樣客戶端就不會再收到和模式相匹配的頻道發(fā)來的信息胀蛮。