服務器(11)--Redis發佈和訂閱(pub/sub)

一、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])

結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章