一、Redis發佈訂閱
Redis 發佈訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
Redis 客戶端可以訂閱任意數量的頻道。
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關係:
當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被髮送給訂閱它的三個客戶端:
二、特點&問題&解決方案
"發佈/訂閱"在redis中,被設計的非常輕量級和簡潔,它做到了消息的“發佈”和“訂閱”的 基本能力;
但是尚未提供關於消息的持久化等各種企業級的特性。
一個Redis client發佈消息,其他多個redis client訂閱消息,發佈的消息“即發即失”,redis 不會持久保存發佈的消息;
消息訂閱者也將只能得到訂閱之後的消息,通道中此前的消息將無 從獲得。
消息發佈者,即publish客戶端,無需獨佔鏈接,你可以在publish消息的同時,使用同一個redis-client鏈接進行其他操作(例如:INCR等)
消息訂閱者,即subscribe客戶端,需要獨佔鏈接,即進行subscribe期間,redis-client無法穿插其他操作, 此時client以阻塞的方式等待“publish端”的消息;因此這裏subscribe端需要使用單獨的鏈接,甚至需要在額外的線程中使用。 Tcp默認連接時間固定,如果在這時間內sub端沒有接收到pub端消息,或pub端沒有消息產生,sub端的連接都會被強制回收, 這裏就需要使用特殊手段解決,用定時器來模擬pub和sub之間的保活機制,定時器時間不能超過TCP最大連接時間,具體根據機器環境來定;
一旦subscribe端斷開鏈接,將會失去部分消息,即鏈接失效期間的消息將會丟失,所以這裏就需要考慮到藉助redis的list來持久化;
如果你非常關注每個消息,那麼你應該基於Redis做一些額外的補充工作,如果你期望訂閱是持久的,那麼如下的設計思路可以借鑑:
1) subscribe端: 首先向一個Set集合中增加“訂閱者ID”, 此Set集合保存了“活躍訂閱”者, 訂閱者ID標記每個唯一的訂閱者,此Set爲 "活躍訂閱者集合"
2) subcribe端開啓訂閱操作,並基於Redis創建一個以 "訂閱者ID" 爲KEY的LIST數據結構, 此LIST中存儲了所有的尚未消費的消息,此List稱爲 "訂閱者消息隊列"
3) publish端: 每發佈一條消息之後,publish端都需要遍歷 "活躍訂閱者集合",並依次 向每個 "訂閱者消息隊列" 尾部追加此次發佈的消息.
4) 到此爲止,我們可以基本保證,發佈的每一條消息,都會持久保存在每個 "訂閱者消息隊列" 中.
5) subscribe端,每收到一個訂閱消息,在消費之後,必須刪除自己的 "訂閱者消息隊列" 頭部的一條記錄.
6) subscribe端啓動時,如果發現自己的 "訂閱者消息隊列" 有殘存記錄, 那麼將會首先消費這些記錄,然後再去訂閱.
以上方法可以保證成功到達的消息必消費不丟失;
三、實例
創建了訂閱頻道名爲 pubSubTest:
現在,我們先重新開啓個 redis 客戶端,然後在同一個頻道 redisChat 發佈兩次消息,訂閱者就能接收到消息。
訂閱者接到的消息:
四、Python中的使用
1、發佈訂閱類
class RedisPubSub:
"""
Redis 發佈訂閱類
:return:
"""
def __init__(self, channel):
self.__conn = redis.Redis(host="127.0.0.1", port=6379, db=0)
self.chan_pub = channel
self.chan_sub = channel
def publish(self, msg):
"""
發佈
:param msg:
:return:
"""
self.__conn.publish(self.chan_pub, msg)
return True
def subscribe(self):
"""
訂閱
:return:
"""
pub = self.__conn.pubsub()
pub.subscribe(self.chan_sub)
pub.parse_response()
return pub
2、發佈
# 發佈
test_id = 2
# 訂閱頻道名稱
obj = RedisPubSub("pubSubTest")
obj.publish('{"test_id":' + str(test_id) + '}')
結果:
3、訂閱
# 訂閱
obj = RedisPubSub("pubSubTest")
redis_sub = obj.subscribe()
while True:
msg = redis_sub.parse_response()
print(msg)
print(msg[2])
結果: