發佈訂閱(pub/sub)是一種消息通信模式:發送者(pub)在某一頻道發送消息,訂閱者(sub)接收消息。發佈訂閱模式類似與微博關注,比如說博主mango被張三、李四、王五關注,那麼mango發一篇微博的時候張李王三人都會從關注裏看到這條微博。
那麼發佈訂閱和生產消費有何異同之處呢?生產消費主要是生成一個消息只能被一個客戶端消費,而發佈訂閱可以理解爲發佈一條消息,在該頻道中的所有客戶端都會收到,所以有時候我們這個發佈訂閱類似廣播。
注意pubsub是一個數據結構。
Pub/Sub(發佈訂閱)
前面提到客戶端能接受到消息首先要訂閱頻道,那麼redis中的訂閱命令subscribe channel [channel ...] 這裏可以訂閱多個頻道。訂閱後我們需要發佈消息到這個頻道中publish channel message。
####訂閱test頻道
> subscribe test
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "test"
3) (integer) 1
####發佈信息
> publish test 'hello'
(integer) 1
不是可以廣播嘛,那麼我們再起一個客戶端
當然redis還提供退訂unsubscribe 跟subscribe 的使用方式一樣。
PUBSUB命令
PUBSUB是一個查看訂閱與發佈系統狀態的內省命令,它由數個不同格式的子命令組成。
PUBSUB CHANNELS [pattern] 列出當前的活躍頻道。活躍頻道指的是那些至少有一個訂閱者的頻道, 訂閱模式的客戶端不計算在內。
pattern 參數是可選的:
-
如果不給出 pattern 參數,那麼列出訂閱與發佈系統中的所有活躍頻道。
-
如果給出 pattern 參數,那麼只列出和給定模式 pattern 相匹配的那些活躍頻道。
> pubsub channels
1) "test"
PUBSUB NUMSUB [channel-1 ... channel-N] 列出當前的活躍頻道和返回連接數
> pubsub numsub test
1) "test" #頻道
2) (integer) 2 #連接數
> pubsub numsub test1
1) "test1"
2) (integer) 0
PUBSUB NUMPAT 返回訂閱模式的數量。
注意,這個命令返回的不是訂閱模式的客戶端的數量,而是客戶端訂閱的所有模式的數量總和,可以理解爲模式匹配,例如訂閱一個test*,客戶端能接收test、test1、test2等等這樣的,看下面例子。
####客戶端
> psubscribe test*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "test*"
3) (integer) 1
> pubsub numpat
(integer) 1
模式匹配訂閱
介紹完發佈訂閱的一般模式,此時我們小夥伴就問,長得跟mango一樣的我能不能訂閱呢?當然redis是支持這種模糊訂閱的,其命令爲psubscribe,跟subscribe使用方式一致。
> psubscribe test*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "test*"
3) (integer) 1
PubSub原理
我們直到redis是key-value鍵值對的字典,PubSub前面講過是一個數據結構,那麼它是如何存儲在內存中的呢?看老夫畫圖來解答。
我們從圖中可以看到每個頻道放入字典數組中,對應頻道的訂閱者則放入鏈表中,當我們發送一個publish命令時,首先字典數組遍歷找到對應的頻道,然後找到對應的訂閱鏈,依次發送消息。
所以我們可以看出每次發送消息時我們的都需要遍歷這個字典,也就是說它的執行時間效率爲O(n),但是我們redis的宗旨是快,減少執行O(n)的命令,這違背了我們當初的初衷
PubSub的缺點
PubSub的發佈者傳遞過來一個消息,Redis會直接找到相應的訂閱者傳遞過去。如果一個訂閱者都沒有,那麼消息會被直接丟棄。如果開始有三個訂閱者,第三個訂閱者突然掛掉了,發佈者會繼續發送消息,另外兩個訂閱者可以持續收到消息,但是當掛掉的訂閱者重新連上的時候,在斷連期間發佈者發送的消息,對於這個發佈者來說就是徹底丟失了。
如果Redis停機重啓,PubSub的消息是不會持久化的,畢竟Redis開機就相當於一個訂閱者都沒有,所有的消息會被直接丟棄。正是因爲PubSub有這些缺點,在消息隊列的領域它幾乎找不到合適的應用場景。
所以Redis的作者單獨開啓了一個項目Disque專門用來做多播消息隊列,不過該項目目前沒有成熟,直處於Beta版本。
填坑——爲什麼Redis不適合做消息隊列
1.難以保證消息隊列的ACK,消息發送出去後沒有一個回饋過程,消息無法做持久化
2.如果保證消息持久化,那麼必定損失性能,首先我們需要把消息存入磁盤,然後從磁盤中讀取數據到內存去操作,這個過程是非常耗時的
3.PubSub執行效率低,執行效率是O(n),違背了redis設計初衷
4.難以實現複雜的消息模式
如果需要用到消息隊列,還得需要使用專業的消息隊列,畢竟這個技術已經相當成熟了
一名正在搶救的coder
筆名:mangolove
CSDN地址:https://blog.csdn.net/mango_love
GitHub地址:https://github.com/mangoloveYu