原理:RedisServer內部維護了一個pubsub_channels字典,其中字典的鍵就是被訂閱的頻道,而鍵值就是訂閱該頻道的客戶端列表;
這樣,當一個客戶端執行PUBLISH channel_name命令時,Redis就可以根據channel_name在pubsub_channels中找到與其關聯的客戶端列表,然後把消息發送給它們,僞代碼;
- def publishCommand(channel, msg):
- # 獲取訂閱channel的所有客戶端列表
- client_list = redisServer.pubsub_channels.get(channel)
- if client_list is None: return
- # 向每個客戶端發送消息
- for client in client_list:
- client.sendMessage(msg)
另外,客戶端自己也維護了一個pubsub_channels屬性,用來記錄自己訂閱了哪些頻道;同watched_keys屬性一樣(詳情請參看 事務章節),客戶端維護這些也是出於效率考慮的:
a. 防止訂閱相同的頻道;
- def subscribeCommand(client, channels):
- for ch in channels:
- # 如果已經訂閱了該頻道,則跳過
- if ch in client.pubsub_channels: continue
- # 把client添加到該頻道關聯的客戶端列表
- client_list = redisServer.pubsub_channels.get(ch)
- client_list.add(client)
- client.pubsub_channels.add(ch)
b. 在UNSUBSCRIBE時,可以快捷的取消該客戶端訂閱的所有頻道,而無需遍歷整個redisServer.pubsub_channels字典,僞代碼:
- def unsubscribeCommand(client):
- # 獲取client訂閱的所有頻道
- channels = client.pubsub_channels
- # 遍歷頻道
- for ch in channels:
- client_list = redisServer.pubsub_channels(ch)
- # 從該頻道關聯的客戶端列表中,刪除client
- client_list.del(client)
- client.pubsub_channels.del(ch)
考慮這麼一個需求:有兩個頻道,名字都以“hello_開頭”,分別叫做hello_1, hello_2;當我們要訂閱這類頻道時,我們可能會這麼寫:SUBSCRIBE hello_1 hello_2,但是如果有100個難道要這樣寫 SUBSCRIBE hello_1 hello_2 ... hello_100? 這時候我們可以使用“模式訂閱”命令PSUBSCRIBE, 譬如這裏我們就可以寫成,PSUBSCRIBE hello_* ;這樣,當一個客戶端執行PUBLISH命令時,redis不僅會把消息發送給所有訂閱該頻道的客戶端列表,同時也會把該頻道與所有模式匹配,如果匹配成功,則把消息同樣發送給訂閱該模式的客戶端列表:
所以完整的PUBLISH命令僞代碼如下:
- def publishCommand(channel, msg):
- # 獲取訂閱channel的所有客戶端列表
- client_list = redisServer.pubsub_channels.get(channel)
- if client_list is None: return
- # 向每個客戶端發送消息
- for client in client_list:
- client.sendMessage(msg)
- # 遍歷pubsub_patterns
- for pattern, client in redisServer.pubsub_patterns:
- # 若模式與channel匹配,則把消息發送給訂閱該模式的客戶端
- if pattern.match(channel):
- client.sendMessage(msg)
更多細節請看:pubsub.c/publishCommand函數
總結:
1. 熟悉發佈訂閱相關命令:subscribe/unsubscribe psubscribe/punsubscribe publish;
2. 瞭解發佈訂閱實現原理;