這一篇我們來看看Redis好玩的發佈訂閱模式,其實在很多的MQ產品中都存在這樣的一個模式,我們常聽到的一個例子就是郵件訂閱的場景,什麼意思呢,也就是說100個人訂閱了你的博客,如果博主發表了文章,那麼100個人就會同時收到通知郵件,除了這個場景還能找到其他場景麼,當然有啦,你想想,如果你要在內存裏面做一個讀寫分離的程序,爲了維持數據的完整性,你是不是需要保證在寫入的時候,也要分發到各個讀內存的程序中呢?所以說場景還是很多的,在於你的挖掘~~~
發佈訂閱(pub/sub)是一種消息通信模式,主要的目的是解耦消息發佈者和消息訂閱者之間的耦合,這點和設計模式中的觀察者模式比較相似。pub /sub不僅僅解決發佈者和訂閱者直接代碼級別耦合也解決兩者在物理部署上的耦合。redis作爲一個pub/sub server,在訂閱者和發佈者之間起到了消息路由的功能。訂閱者可以通過subscribe和psubscribe命令向redis server訂閱自己感興趣的消息類型,redis將消息類型稱爲通道(channel)。當發佈者通過publish命令向redis server發送特定類型的消息時。訂閱該消息類型的全部client都會收到此消息。這裏消息的傳遞是多對多的。一個client可以訂閱多個 channel,也可以向多個channel發送消息。
下面還是從基本命令入手:
PSUBSCRIBE pattern [pattern ...] #訂閱一個或多個符合給定模式的頻道;
PUBSUB subcommand [argument [argument ...]] #查看訂閱與發佈系統狀態;
PUBLISH channel message #將信息發送到指定的頻道;
PUNSUBSCRIBE [pattern [pattern ...]] #退訂所有給定模式的頻道;
SUBSCRIBE channel [channel ...] #訂閱給定的一個或多個頻道的信息;
UNSUBSCRIBE [channel [channel ...]] #指退訂給定的頻道;
從redis手冊上面可以看到,其實“發佈、訂閱”模式才區區6個命令,下面聽我一一解說下
SUBSCRIBE
訂閱給定的一個或多個頻道的信息。
SUBSCRIBE channel [channel ...]
從上面的官方解釋上來看,它的玩法有一點像現實生活中我們聽收音機一個道理,要想聽收音機,我們要做什麼?肯定就是調頻啦,只有在正確的頻道上面,我們才能聽得到好聽的節目,所以說subscribe首先要訂閱一個頻道(channel),下面我舉個例子,開兩個client,分別訂閱着msg這個頻道,比如下面這樣:
root@localhost:~ # redis-cli -p 6379
127.0.0.1:6379> SUBSCRIBE msg
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "msg"
3) (integer) 1
root@localhost:~ # redis-cli -p 6379
127.0.0.1:6379> SUBSCRIBE msg
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "msg"
3) (integer) 1
SUBSCRIBE還可以訂閱多個頻道,這樣一來它接收到的信息就可能來自多個頻道。
PUBLISH
到現在爲止,這兩個subscibe都在監視着msg這個頻道,接下來,如果msg頻道有消息傳出,必定會被subscribe接收到,我們還是先看看redis手冊上怎麼用這個命令。
將信息message發送到指定的頻道channel。
PUBLISH channel message
如下演示:
看到麼有,publish在msg這個頻道上面發送消息後,被subscribe監視到了,然後就被分別打印輸出了,好了,到現在爲止,最基本的發佈訂閱模式就是這樣,是不是很簡單哈。其實呢??? 也就是這麼簡單吶,但是呢,有時候我們還有這樣一個需求,就是我能不能模糊匹配key呢?舉了例子,就是要求訂閱china爲前綴的所有頻道,如果這樣也可以做到的話,那確實是很牛逼啦。。。我要是回答的話,當然啦,強大的Redis自然會做到這一點,它提供了的命令就是:PSUBSCRIBE。
PSUBSCRIBE
訂閱一個或多個符合給定模式的頻道,每個模式以作爲匹配符,比如it匹配所有以it開頭的頻道(it.news、it.blog、it.tweets等等),news.*匹配所有以news.開頭的頻道(news.it、 news.global.today 等等),諸如此類。
PSUBSCRIBE pattern [pattern ...]
看到上面的解釋,你心裏可能就在想,這不就是正則匹配麼。。。而且前綴”P”就是Pattern的意思,對吧,接下來我就訂閱一下所有china爲前綴的channel。
當然,PSUBSCRIBE 也可以接受多個參數,從而匹配多種模式。看完一個小例子後應該對pub/sub功能有了一個感性的認識,需要注意的是當一個連接通過subscribe或者psubscribe訂閱通道後就進入訂閱模式。在這種模式除了再訂閱額外的通道或者用unsubscribe或者punsubscribe命令退出訂閱模式,就不能再發送其他命令。另外使用 psubscribe命令訂閱多個通配符通道,如果一個消息匹配上了多個通道模式的話,會多次收到同一個消息。
Redis的pub/sub還是有點太單薄(實現才用150行代碼)。在安全,認證,可靠性這方便都沒有太多支持。
Redis發佈/訂閱機制
當一個客戶端通過 PUBLISH 命令向訂閱者發送信息的時候,我們稱這個客戶端爲發佈者(publisher)。
而當一個客戶端使用 SUBSCRIBE 或者 PSUBSCRIBE 命令接收信息的時候,我們稱這個客戶端爲訂閱者(subscriber)。
爲了解耦發佈者(publisher)和訂閱者(subscriber)之間的關係,Redis 使用了 channel (頻道)作爲兩者的中介 —— 發佈者將信息直接發佈給 channel ,而 channel 負責將信息發送給適當的訂閱者,發佈者和訂閱者之間沒有相互關係,也不知道對方的存在:
知道了發佈和訂閱的機制之後,接下來就可以開始研究具體的實現了,我們從Redis的訂閱命令開始說起。
SUBSCRIBE命令的實現
前面說到,Redis將所有接受和發送信息的任務交給channel來進行,而所有channel的信息就儲存在redisServer這個結構中:
struct redisServer {
// 省略 ...
dict *pubsub_channels; // Map channels to list of subscribed clients
// 省略 ...
};
pubsub_channels是一個字典,字典的鍵就是一個個channel,而字典的值則是一個鏈表,鏈表中保存了所有訂閱這個channel的客戶端。
舉個例子,如果在一個 redisServer 實例中,有一個叫做 news 的頻道,這個頻道同時被client_123 和 client_456 兩個客戶端訂閱,那麼這個 redisServer 結構看起來應該是這樣子:
可以看出,實現SUBSCRIBE命令的關鍵,就是將客戶端添加到給定channel的訂閱鏈表中。
PSUBSCRIBE命令的實現
除了直接訂閱給定channel外,還可以使用PSUBSCRIBE訂閱一個模式(pattern),訂閱一個模式等同於訂閱所有匹配這個模式的channel 。
和redisServer.pubsub_channels屬性類似,redisServer.pubsub_patterns屬性用於保存所有被訂閱的模式,和pubsub_channels不同的是, pubsub_patterns是一個鏈表(而不是字典):
struct redisServer {
// 省略 ...
list *pubsub_patterns; // A list of pubsub_patterns
// 省略 ...
};
pubsub_patterns 的每一個節點都是一個 pubsubPattern 結構的實例,它保存了被訂閱的模式,以及訂閱這個模式的客戶客戶端:
typedef struct pubsubPattern {
redisClient client;
robj pattern;
} pubsubPattern;
舉個例子,假設在一個 redisServer 實例中,有一個叫做 news.* 的模式同時被客戶端client_789 和 client_999 訂閱,那麼這個 redisServer 結構看起來應該是這樣子:
現在可以知道,實現PSUBSCRIBE命令的關鍵,就是將客戶端和訂閱的模式添加到redisServer.pubsub_patterns當中。