Redis的發(fā)布與訂閱
Redis的發(fā)布與訂閱功能由PUBLISH、SUBSCRIBE肾请、PSUBSCRIBE等命令組成。
1 頻道的訂閱與退訂
1.1訂閱頻道
命令:subscribe channel [channel..]
如subscribe “news.it”表示訂閱了"news.it"頻道
通過執(zhí)行SUBSCRIBE命令挽鞠,客戶端可以訂閱一個或多個頻道闹究,從而成為這些頻道的訂閱者(subscriber):每當有其他客戶端向被訂閱的頻道發(fā)送消息時(message)時霜瘪,頻道的所有訂閱者都會收到這條消息珠插。
當一個客戶端執(zhí)行SUBSCRIBE命令訂閱某個或某些頻道時,這個客戶端與被訂閱頻道之間就建立起了一種訂閱關系颖对。
Redis將所有頻道的訂閱關系都保存在服務器狀態(tài)的pubsub_channels字典(可以理解為redisServer中的一個map屬性)里面捻撑,這個字典的鍵是某個被訂閱的頻道,而值是一個鏈表缤底,鏈表的里面記錄了所有訂閱這個頻道的客戶端顾患。
struct redisServer{
// ...
// 保存所有頻道的訂閱關系
dict *pubsub_channels;
// ...
}
例如,客戶端1个唧、2江解、3訂閱了"news.it"頻道,客戶端4訂閱了"news.sport"頻道徙歼,客戶端5犁河、6訂閱了“news.bussiness”頻道,那么在pubsub_channels字典中的結構就如下圖所示魄梯。
每當客戶端執(zhí)行SUBSCRIBE命令訂閱某個或某些頻道時桨螺,服務器都會根據(jù)客戶端與被訂閱的頻道在pubsub_channels字典中進行關聯(lián)。
- 如果頻道已經(jīng)有其他訂閱者酿秸,那么他在pubsub_channels字典中必然有相應的訂閱者鏈表灭翔,程序要做的就是將客戶端添加到訂閱者鏈表的末尾。
-
如果頻道還沒有任何訂閱者辣苏,那么程序首先要在pubsub_channels字典中為頻道創(chuàng)建一個鍵肝箱,并將這個鍵的值設置為空鏈表,然后再將客戶端添加到鏈表稀蟋,成為鏈表的第一個元素狭园。
如果客戶端7訂閱了news.sport和news.movie頻道,則pubsub_channels字典結構變成下圖所示糊治。
1.2退訂頻道
UNSUBSCRIBE命令的行為和SUBSCRIBE命令的行為正好相反唱矛,當一個客戶端退訂某個或某些頻道的時候,服務器將從pubsub_channels中解除客戶端與被退訂頻道之間的關聯(lián):
- 程序會根據(jù)被退訂頻道的名字,在pubsub_channels字典中找到頻道對應的訂閱鏈表绎谦,然后從訂閱鏈表中刪除退訂客戶端的信息管闷。
- 如果刪除退訂客戶端后,頻道的訂閱鏈表變?yōu)榭真湵砬猿Γ敲凑f明這個頻道已經(jīng)沒有任何訂閱者了包个,程序?qū)膒ubsub_channels字典中刪除頻道對應的鍵。
2 模式的訂閱與退訂
2.1訂閱模式
前面說到冤留,服務器將所有頻道的訂閱關系都保存在服務器狀態(tài)的pubsub_channels屬性里面碧囊,與此類似,服務器也會將所有模式的訂閱關系保存在服務器狀態(tài)的pubsub_patterns屬性里纤怒。
struct redisServer{
// ...
// 保存所有的訂閱模式
list *pubsub_patterns;
// ...
}
pubsub_patterns屬性是一個鏈表糯而,鏈表中的每個節(jié)點都包含著一個pubsub_pattern結構,這個結構的pattern屬性記錄了被訂閱的模式泊窘,而client屬性記錄了訂閱模式的客戶端:
typedef struct pubsubPattern{
// 訂閱模式的客戶端
redisClient *client;
// 被訂閱的模式
robj *pattern;
}pubsubPattern
例如熄驼,客戶端8、9烘豹、10分別訂閱了"music.* "瓜贾、"book.* " 、"news.* "模式携悯,那么在pubsub_patterns鏈表結構就如下圖所示
每當客戶端執(zhí)行PSUBSCIRBE命令訂閱某個或某些模式的時候祭芦,服務器會對每個訂閱模式執(zhí)行以下兩個操作:
- 新建一個pubsubPattern結構,將結構的pattern屬性設置為訂閱的模式憔鬼,client屬性設置為被訂閱模式的客戶端实束。
- 將pubsubPattern結構添加到pubsub_patterns鏈表的表尾部。
2.2 退訂模式
退訂模式和退訂頻道的原理類似逊彭,都是對鏈表節(jié)點刪除操作咸灿,這里不再累述。
3 發(fā)送消息
當一個Redis客戶端執(zhí)行PUBLISH <channel> <message>命令將消息發(fā)送給頻道<channel>的時候侮叮,服務器執(zhí)行以下兩個動作:
(1) 將消息message發(fā)送給channel頻道的所有訂閱者避矢。
(2) 如果有一個或多個模式pattern與頻道channel相匹配,那么消息message也會發(fā)送給pattern模式的訂閱者囊榜。
例如审胸,client-1訂閱了“news.it”頻道,client-2訂閱了模式"news.* "卸勺,那么當有客戶端執(zhí)行publish "news.it" “hello”砂沛,那么client-1和client-2都會收到消息。
3.1 將消息發(fā)送給頻道訂閱者
因為服務器狀態(tài)中的pubsub_channels字典記錄了所有頻道的訂閱關系曙求,所以為了將消息發(fā)送給channel頻道的所有訂閱者碍庵, PUBLISH命令要做的就是在pubsub_ channels字典里找到頻道channel的訂閱者名單(一個鏈表),然后將消息發(fā)送給名單上的所有客戶端静浴。
例如堰氓,某個客戶端執(zhí)行命令
publish "news.it" "hello"
那么PUBLISH命令會在pubsub_channels字典中查找鍵"news.it"對應的鏈表值,并通過遍歷鏈表將消息"hello"發(fā)送給client-1苹享、client-2双絮、client-3。
3.2將消息發(fā)送給模式訂閱者
因為服務器狀態(tài)中的pubsub_patterns鏈表記錄了所有模式的訂閱關系得问,所以為了將消息發(fā)送給所有與channel頻道相匹配的模式的訂閱者囤攀,PUBLISH命令要做的就是遍歷整個pubsub_patterns鏈表, 查找那些與channel頻道相匹配的模式宫纬, 并將消息發(fā) 送給訂閱了這些模式的客戶端焚挠。
例如,某個客戶端執(zhí)行命令
publish "news.it" "hello"
那么PUBLISH命令會首先將消息"hello"發(fā)送給"news.it"頻道的所有訂閱者哪怔,然后開始在pubsub_patterns鏈表中查找是否有被訂閱的模式與"news.it"頻道相匹配, 結果發(fā)現(xiàn)"news.it"頻道和客戶端client-10訂閱的"news.* "頻道匹配向抢, 于是命令將消息"hello"發(fā)送給客戶端client-10认境。
4 查看訂閱信息
PUBSUB命令是Redis 2.8新增的命令,客戶端可以通過這個命令查看頻道或者模式的相關信息挟鸠。
4.1PUBSUB CHANNELS
PUBSUB CHANNELS [pattern]子命令用于返回服務器當前被訂閱的頻道叉信,其中pattern參數(shù)是可選的:
- 如果不給定,則返回服務器當前被訂閱的所有頻道艘希。
- 如果給定硼身,那么命令返回服務器當前被訂閱的頻道中那些與pattern模式相匹配的頻道。
這個子命令通過遍歷服務器pubsub_channels字典的所有鍵(每個鍵都是一個被訂閱的頻道)覆享,然后記錄并返回所有符合條件的頻道來實現(xiàn)佳遂。
對于下圖,如果執(zhí)行pubsub channels撒顿,將返回:
(1) "news.it"
(2) "news.sport"
(3) "news.bussiness"
如果執(zhí)行pubsub channels “news.[is]* ”,命令將返回:
(1) "news.it"
(2) "news.sport"
4.2 PUBSUB NUMSUB
PUBSUB NUMSUB [channel-1 channel-2 ...]子命令接受任意多個頻道作為輸入?yún)?shù)丑罪,并返回這些頻道的訂閱者數(shù)量。
這個子命令通過在pubsub_channels字典中找到頻道對應的訂閱者鏈表凤壁,然后返回訂閱者鏈表的長度來實現(xiàn)的(訂閱者鏈表的長度就是頻道訂閱者的數(shù)量)吩屹。
例如,對上圖的pubsub_channels字典實例結構執(zhí)行:pubsub numsub "news.it" "news sport" "news bussiness",將返回:
1) "news.it"
2) "3"
3) "news.sport"
4) "1"
5) "news.sport"
6) "2"
4.3 PUBSUB NUMPAT
PUBSUB NUMPAT子命令用于返回服務器當前訂閱模式的數(shù)量拧抖。
這個子命令 通過返回pubsub_patterns鏈表的長度來實現(xiàn)的煤搜,因為這個鏈表的長度就是服務器訂閱模式的數(shù)量。
對于下圖唧席,如果執(zhí)行pubsub numpat擦盾,將返回
(integer) 3
5 小結
(1) 服務器狀態(tài)在pubsub_channels字典保存了所有頻道的訂閱關系嘲驾,在pubsub_patterns鏈表中保存了所有模式的訂閱關系。
(2) 客戶端向一個頻道發(fā)送一條消息時厌衙,不僅所有訂閱了該頻道的客戶端會接收到這條消息距淫,與該頻道相匹配的訂閱模式的客戶端也會收到這條消息。
(3) PUBSUB命令的三個子命令都是通過讀取pubsub_patterns鏈表或pubsub_channels字典中的信息來實現(xiàn)的婶希。
本文完
注:本文參考《Redis設計與實現(xiàn)》榕暇,如發(fā)現(xiàn)錯誤,請指正喻杈!