Reference: https://redisbook.readthedocs.io/en/latest/feature/pubsub.htmlhtml
Redis 的 SUBSCRIBE 命令可讓客戶端訂閱任意數量的頻道, 每當有新信息發送到被訂閱的頻道時, 信息就會被髮送給全部訂閱指定頻道的客戶端。node
做爲例子, 下圖展現了頻道 channel1
, 以及訂閱這個頻道的三個客戶端 —— client2
、 client5
和 client1
之間的關係:python
當有新消息經過 PUBLISH 命令發送給頻道 channel1
時, 這個消息就會被髮送給訂閱它的三個客戶端:redis
在後面的內容中, 咱們將探討 SUBSCRIBE 和 PUBLISH 命令的實現, 以及這套訂閱與發佈機制的運做原理。服務器
每一個 Redis 服務器進程都維持着一個表示服務器狀態的 redis.h/redisServer
結構, 結構的 pubsub_channels
屬性是一個字典, 這個字典就用於保存訂閱頻道的信息:app
struct redisServer { // ... dict *pubsub_channels; // ... };
其中,字典的鍵爲正在被訂閱的頻道, 而字典的值則是一個鏈表, 鏈表中保存了全部訂閱這個頻道的客戶端。spa
好比說,在下圖展現的這個 pubsub_channels
示例中, client2
、 client5
和 client1
就訂閱了 channel1
, 而其餘頻道也分別被別的客戶端所訂閱:code
當客戶端調用 SUBSCRIBE 命令時, 程序就將客戶端和要訂閱的頻道在 pubsub_channels
字典中關聯起來。server
舉個例子,若是客戶端 client10086
執行命令 SUBSCRIBE channel1 channel2 channel3
,那麼前面展現的 pubsub_channels
將變成下面這個樣子:htm
SUBSCRIBE 命令的行爲能夠用僞代碼表示以下:
def SUBSCRIBE(client, channels): # 遍歷全部輸入頻道 for channel in channels: # 將客戶端添加到鏈表的末尾 redisServer.pubsub_channels[channel].append(client)
經過 pubsub_channels
字典, 程序只要檢查某個頻道是否爲字典的鍵, 就能夠知道該頻道是否正在被客戶端訂閱; 只要取出某個鍵的值, 就能夠獲得全部訂閱該頻道的客戶端的信息。
瞭解了 pubsub_channels
字典的結構以後, 解釋 PUBLISH 命令的實現就很是簡單了: 當調用 PUBLISH channel message
命令, 程序首先根據 channel
定位到字典的鍵, 而後將信息發送給字典值鏈表中的全部客戶端。
好比說,對於如下這個 pubsub_channels
實例, 若是某個客戶端執行命令 PUBLISH channel1 "hello moto"
,那麼 client2
、 client5
和 client1
三個客戶端都將接收到 "hello moto"
信息:
PUBLISH 命令的實現能夠用如下僞代碼來描述:
def PUBLISH(channel, message): # 遍歷全部訂閱頻道 channel 的客戶端 for client in server.pubsub_channels[channel]: # 將信息發送給它們 send_message(client, message)
使用 UNSUBSCRIBE 命令能夠退訂指定的頻道, 這個命令執行的是訂閱的反操做: 它從 pubsub_channels
字典的給定頻道(鍵)中, 刪除關於當前客戶端的信息, 這樣被退訂頻道的信息就不會再發送給這個客戶端。
當使用 PUBLISH 命令發送信息到某個頻道時, 不只全部訂閱該頻道的客戶端會收到信息, 若是有某個/某些模式和這個頻道匹配的話, 那麼全部訂閱這個/這些頻道的客戶端也一樣會收到信息。
下圖展現了一個帶有頻道和模式的例子, 其中 tweet.shop.*
模式匹配了 tweet.shop.kindle
頻道和 tweet.shop.ipad
頻道, 而且有不一樣的客戶端分別訂閱它們三個:
當有信息發送到 tweet.shop.kindle
頻道時, 信息除了發送給 clientX
和 clientY
以外, 還會發送給訂閱 tweet.shop.*
模式的 client123
和 client256
:
另外一方面, 若是接收到信息的是頻道 tweet.shop.ipad
, 那麼 client123
和 client256
一樣會收到信息:
redisServer.pubsub_patterns
屬性是一個鏈表,鏈表中保存着全部和模式相關的信息:
struct redisServer { // ... list *pubsub_patterns; // ... };
鏈表中的每一個節點都包含一個 redis.h/pubsubPattern
結構:
typedef struct pubsubPattern { redisClient *client; robj *pattern; } pubsubPattern;
client
屬性保存着訂閱模式的客戶端,而 pattern
屬性則保存着被訂閱的模式。
每當調用 PSUBSCRIBE
命令訂閱一個模式時, 程序就建立一個包含客戶端信息和被訂閱模式的 pubsubPattern
結構, 並將該結構添加到 redisServer.pubsub_patterns
鏈表中。
做爲例子,下圖展現了一個包含兩個模式的 pubsub_patterns
鏈表, 其中 client123
和 client256
都正在訂閱 tweet.shop.*
模式:
若是這時客戶端 client10086
執行 PSUBSCRIBE broadcast.list.*
, 那麼 pubsub_patterns
鏈表將被更新成這樣:
經過遍歷整個 pubsub_patterns
鏈表,程序能夠檢查全部正在被訂閱的模式,以及訂閱這些模式的客戶端。
發送信息到模式的工做也是由 PUBLISH 命令進行的, 在前面講解頻道的時候, 咱們給出了這樣一段僞代碼, 說它定義了 PUBLISH 命令的行爲:
def PUBLISH(channel, message): # 遍歷全部訂閱頻道 channel 的客戶端 for client in server.pubsub_channels[channel]: # 將信息發送給它們 send_message(client, message)
可是,這段僞代碼並無完整描述 PUBLISH 命令的行爲, 由於 PUBLISH 除了將 message
發送到全部訂閱 channel
的客戶端以外, 它還會將 channel
和 pubsub_patterns
中的模式進行對比, 若是 channel
和某個模式匹配的話, 那麼也將 message
發送到訂閱那個模式的客戶端。
完整描述 PUBLISH 功能的僞代碼定於以下:
def PUBLISH(channel, message): # 遍歷全部訂閱頻道 channel 的客戶端 for client in server.pubsub_channels[channel]: # 將信息發送給它們 send_message(client, message) # 取出全部模式,以及訂閱模式的客戶端 for pattern, client in server.pubsub_patterns: # 若是 channel 和模式匹配 if match(channel, pattern): # 那麼也將信息發給訂閱這個模式的客戶端 send_message(client, message)
舉個例子,若是 Redis 服務器的 pubsub_patterns
狀態以下:
那麼當某個客戶端發送信息 "Amazon Kindle, $69."
到 tweet.shop.kindle
頻道時, 除了全部訂閱了 tweet.shop.kindle
頻道的客戶端會收到信息以外, 客戶端 client123
和 client256
也一樣會收到信息, 由於這兩個客戶端訂閱的 tweet.shop.*
模式和 tweet.shop.kindle
頻道匹配。
使用 PUNSUBSCRIBE 命令能夠退訂指定的模式, 這個命令執行的是訂閱模式的反操做: 程序會刪除 redisServer.pubsub_patterns
鏈表中, 全部和被退訂模式相關聯的 pubsubPattern
結構, 這樣客戶端就不會再收到和模式相匹配的頻道發來的信息。
redisServer.pubsub_channels
字典保存,字典的鍵爲被訂閱的頻道,字典的值爲訂閱頻道的全部客戶端。redisServer.pubsub_patterns
鏈表保存,鏈表的每一個節點都保存着一個 pubsubPattern
結構,結構中保存着被訂閱的模式,以及訂閱該模式的客戶端。程序經過遍歷鏈表來查找某個頻道是否和某個模式匹配。