redis學習(七) 訊息通知

z1340954953發表於2018-03-05

場景(可以略過不看,只是引入的場景)

郵件訂閱的,在部落格首頁放入一個文字框供訪客輸入自己的郵箱地址,提交後部落格會將該地址存入redis的一個集合型別的鍵中(使用集合型別是為了保證同一郵箱地址不會儲存多個),每當釋出新文章時,就向收集到的郵箱地址傳送通知郵件。

問題:輸入郵箱地址提交後,頁面需要很久時間才能載入完。

原因:原來小白為了保證使用者沒有輸入他人的郵箱,在提交之後程式會向使用者輸入的郵箱傳送一封包含確認資訊的郵件,只有使用者單擊這個連線後對應的郵箱地址才會被記錄。可是由於傳送郵件需要連線到一個遠端的郵件傳送伺服器,至少需要2秒。

結果就是,每次使用者提交郵箱後頁面都需要等待程式傳送郵件才能載入出來,載入出來的頁面只是提示使用者確認郵箱地址。完全可以等頁面載入出來,再傳送郵件,這樣使用者就不需要等待了。

任務佇列

網站開發中,當頁面需要進行如傳送郵件、複雜資料運算等耗時較長的操作時會阻塞頁面的渲染。為了避免使用者等待太久,應該使用獨立的執行緒來完成這類操作。就上面發郵件的例子來說,設想有一個程式能夠完成傳送郵件的功能,那麼在頁面中只需要想辦法通知這個程式向指定的地址傳送郵件就可以了。

通知的過程可以藉助任務佇列來實現。任務佇列,就是傳遞任務的佇列。與任務佇列進行互動的實體有兩類,一類是生產者,一類是消費者。生產者將任務放到任務佇列中,消費者則不斷從任務佇列中讀取任務並執行。

就上面郵件的例子來說,頁面將收件地址、郵件主題和郵件正文組裝成任務後存入任務佇列中。同時發郵件的程式不斷檢查任務佇列並執行。

優點:

1> 鬆耦合。生產者和消費者無需知道彼此的實現細節,只要約定好任務的描述格式,使得生產者和消費者可以有不同的程式語言編寫。

2> 易於擴充套件。消費者可以有多個,而且可以分佈在不同的伺服器中。可以降低單臺伺服器的負載。


如何使用redis實現任務佇列

前面有介紹過redis的列表型別可以實現佇列,lpush命令加入任務,rpop命令取出任務。每次去取任務時候需要檢查

是否有任務,取出任務例項等待1秒,在去消費(避免頻繁消費任務)


優化

藉助brpop命令,實現一旦有新任務加入任務佇列就通知消費者。BRPOP和RPOP命令的,唯一區別是當列表中沒有元素時候,

BROP命令會阻塞住連線,直到有新元素加入。

loop
#如果任務佇列中沒有新任務,BRPOP命令會一直阻塞,不會執行execute()
$task = BRPOP queue,0
#返回值是一個陣列,陣列第二個元素是我們需要的任務
execute($task[1])

BRPOP命令接受兩個引數,第一個是鍵名,第二個是超時時間,單位是秒。當超過了時間仍然沒有獲得新元素的話就會返回nil

 ,超時時間是0,表示不限制等待時間,永遠阻塞下去。

優先順序佇列

BRPOP命令可以同時接收多個鍵,其完整的命令格式為BLPOP key[key...] timeout, 如BLPOP queue:1 queue:2 0 意義是同時檢測多個鍵,如果所有鍵都沒有元素則阻塞,如果其中一個鍵有元素則會從該鍵中彈出元素。

並且存在多個鍵都有元素的情況,則按照從左到右的順序取第一個鍵中的一個元素。這樣就實現了優先佇列

釋出/訂閱模式

釋出/訂閱模式也能實現程式間的訊息傳遞,原理:

釋出/訂閱模式中包含兩種角色,分別是釋出者和訂閱者。訂閱者可以訂閱一個或者若干個頻道,而釋出者可以向指定的頻道傳送訊息,所有訂閱此頻道的訂閱者都會受到訊息。

釋出:PUBLISH  用法是: PUBLISH channel message , 返回值表示收到這條訊息的訂閱者數量。

因為此時沒有客戶端訂閱channel1.1 ,所以返回0,發出去的訊息不會被持久化,也就是說當有客戶端訂閱channel1.1後只能收到後續釋出到該頻道的訊息,之前傳送的就收不到了。

訂閱SUBSCRIBE,可以同時訂閱多個頻道,用法是SUBSCRIBE channel[channel ...] 

SUBSCRIBE channel1.1 在執行subscribe命令後客戶端會進入訂閱狀態,處在此狀態下客戶端不能使用除了

SUBSCRIBE,UNSUBSCRIBE,PSUBSCRIBE和PUNSUBSCRIBE這4個屬於釋出/訂閱模式命令之外的命令,否則會報錯

進入訂閱狀態後的客戶端可能收到3中型別的回覆,每種型別的回覆都包含3個值,第一個值是訊息的型別,根據訊息型別的不同,第二、第三值的含義不同。訊息型別的可能取值:

1> subscribe。表示訂閱成功的反饋資訊。第二個值訂閱成功的頻道名稱。第三個值是當前客戶端訂閱頻道數量

2> message。這種型別的回覆是我們最關心的。表示接受到的訊息。第二個值表示產生訊息的頻道名稱。第三個值為訊息內容

3> unsubscribe。表示成功取消訂閱某個頻道。第二值是對應的頻道名稱。第三個值是客戶端訂閱的頻道數量。當此值為0時客戶端會退出訂閱狀態,就能執行其他非釋出/訂閱命令。

UNSUBSCRIBE命令取消訂閱的頻道。UNSUBSCRIBE [channel ...],不指定頻道則會取消訂閱所有頻道。

按照規則訂閱

PSUBSCRIBE命令訂閱指定的規則,支援glob風格萬用字元格式

PSUBSCRIBE channel.?*, 規則channel.?* 可以匹配channel1.1和channel1.10,但是不會匹配channel.。

如果有 redis客戶端B,釋出訊息PUBLIST channel1.1 hi! 

客戶端C通過psubscribe訂閱了channel,就會收到訊息回覆。

1> pmessage (表示通過PSUBSCRIBE命令訂閱頻道收到的)

2> channel1.?*(表示訂閱使用的萬用字元)

3> channel1.1(表示實際收到訊息的頻道命令)

4> hi!(訊息內容)

note : 

1. 使用PSUBSCRIBE命令可以重複訂閱一個頻道,如果客戶端執行了PSUBSCRIBE channel1.? channel1.?*,這時候向channel1.2釋出訊息後會收到兩條訊息,並且PUBLISH命令的返回值是2

同樣的,如果有另一個客戶端執行了subscribe channel1.10 和 psubscribe channel.?*的話,向channel1.19傳送訊息,改客戶端也會收到兩條訊息(message和pmessage)

2. PUNSUBSCRIBE命令可以推定指定的規則,PUNSUBSCRIBE [pattern] ,無引數,則會退訂所有規則

note: punsubscribe命令只能退訂通過psubscribe命令訂閱的規則,不影響subscribe訂閱的頻道,同樣unsubscribe命令也不會影響psubscribe命令訂閱的規則。容易出錯的一點是使用punsubscribe命令退訂某個規則是不會將其中的萬用字元展開,而是進行嚴格的字串匹配。PUNSUBSCRIBE * 無法退訂channel.* 規則,必須使用PUNSUBSCRIBE channel.*才能退訂

管道

redis的底層通訊協議對管道提供了支援,通過管道可以一次傳送多個命令,並執行完後一次性將結果返回,當一組命令中每個命令都不會依賴之前命令的執行,就可藉助管道,減少客戶端和redis的通訊次數

省空間

精簡鍵名和鍵值

內部編碼優化(redis會自動調整)



相關文章