Redis高階進階(二)
一、訊息通知
在一些網站上,經常會有一些釋出/訂閱或者郵件訂閱的功能,尤其一些部落格上。其實這種問題很常見,當頁面需要進行如傳送郵件、複雜的計算時會阻塞頁面的渲染。為了避免使用者等待太久,應該使用其他程序單獨完成此類操作,這裡郵件訂閱可以用任務佇列來實現,具體來說,當需要傳送郵件時,將其存入佇列中,另外一個程序監視該佇列,一旦發現就讀取資訊進行傳送郵件。
1、使用redis實現任務佇列
在redis中我們很容易想到使用列表來實現佇列是最好不過的了,這時生產者通過lpush往列表中新增郵件資訊,另外消費者通過rpop進行讀取郵件資訊進而傳送郵件。
實現的虛擬碼如下:
#無限迴圈 loop $task = rpop queue if $task execute($task) else wait 1 second
以上就簡單的實現了一個任務佇列,這裡有點不足的地方就是:如果任務列表中沒有通知任務,這時還是通過每秒執行rpop進行檢查,如果能實現一旦有新任務就通知消費者來讀取就最好不過了,BRPOP命令就可以很好的實現該需求,brpop和rpop命令類似,唯一區別在於brpop會在列表中沒有元素時一直阻塞連線,直到有新元素加入,以上的程式碼可以修改為:
loop $task = brpop queue,0 execute($task)
brpop語法:brpop Key[key...] timeout
接受兩個引數,第一個是key,可以有多個。第二個引數是超時(秒),超過這個時間後會返回nil。當設定為0表示沒有時間限制,如果沒有新元素加入就一直阻塞。
為了測試brpop命令,我們開啟兩個session:
session A:
127.0.0.1:6379> brpop queue 0 #一直監視queue內的元素情況,一旦session B中加入一個元素後立馬輸出下面的資訊 1) "queue" 2) "10" (27.40s)
session B:
127.0.0.1:6379> lpush queue 10 (integer) 1
這時再檢視queue列表中的情況:
127.0.0.1:6379> lrange queue 0 -1 #已經被取走 (empty list or set)
2、優先順序佇列
假設某個部落格有10000個郵件訂閱者,那麼當釋出一篇新文章需要向任務佇列中新增10000個任務,如果發一個郵件需要10秒,全部完成這些任務需要30個小時。問題來了,如果這時有個新的訂閱者,需要傳送確認郵件,它根本就不知道前面排了10000個任務呢,那麼他不得不等30個小時完成確認,多麼糟糕的使用者體驗!而另一方面傳送文章通知郵件並不是緊急的,有時晚一天也可以接受的,所以可以得出結論,當二者同時出現時,應該優先執行確認郵件的任務,為了實現這個需求,我們必須完成一個優先順序佇列。
幸福的是BRPOP命令是可以實現的,由於BRPOP可以接受多個key,如brpop queue1 queue2 0,意思是同時監控多個key,一旦有哪個鍵有新元素加入就彈出,如果多個鍵都有新元素加入,那麼會按照從左到右的順序取第一個鍵中的元素。下面進行測試:
127.0.0.1:6379> lpush queue1 10 (integer) 1 127.0.0.1:6379> lpush queue2 20 (integer) 1 127.0.0.1:6379> lpush quequ3 30 (integer) 1 127.0.0.1:6379> lpush queue1 11 (integer) 2 127.0.0.1:6379> lpush queue1 12 (integer) 3 127.0.0.1:6379> brpop queue1 queue2 queue3 0 1) "queue1" 2) "10" 127.0.0.1:6379> brpop queue1 queue2 queue3 0 1) "queue1" 2) "11" 127.0.0.1:6379> brpop queue1 queue2 queue3 0 #到這裡完全是按從左到右的順序,將第一個key中元素全部取完才輪到下一個key 1) "queue1" 2) "12" 127.0.0.1:6379> brpop queue1 queue2 queue3 0 1) "que
通過以上的特性,我們可以建立兩個佇列:分別是queue.confirm.email和queue.notify.email,下面是虛擬碼:
loop $task = brpop queue.confirm.email queue.notify.email 0 execute($task[1])
3、釋出/訂閱模式
除了實現佇列外,redis還提供一組命令可以讓開發者實現釋出/訂閱模式。釋出/訂閱模式同樣可以實現程序間資訊通訊。它的原理是這樣的:
釋出/訂閱包含兩種角色,分別是釋出者和訂閱者。訂閱者可以訂閱一個或若干個頻道,而釋出者可以針對頻道進行傳送訊息。
釋出者釋出訊息的命令是:publish channel message 返回值是訂閱者的數量。
訂閱者訂閱的命令是:subscribe channel [channel...]
下面開啟兩個session進行測試:
session A:訂閱頻道1.1
127.0.0.1:6379> subscribe channel1.1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel1.1" 3) (integer) 1 1) "message" 2) "channel1.1" 3) "helloworld" 1) "message" 2) "channel1.1" 3) "darren"
session B:釋出者
127.0.0.1:6379> publish channel1.1 helloworld (integer) 1 127.0.0.1:6379> publish channel1.1 darren (integer) 1
4、管道
客戶端和redis server使用TCP協議連線。不論是客戶端傳送命令到redis還是redis返回結果給客戶端,都需要經過網路傳輸,這兩部分總消耗稱為往返時延。當執行命令很多時,各個執行的往返時延加起來還是對效能有一定影響的。因為在執行多條命令時,每條命令都要等到上一條命令執行完成並返回結果才能執行,所以redis提供管道功能,可以一次性
傳送多個命令,而且等都執行完成後一次性返回結果,這樣就減少了每條命令都需要的往返時延了,可以節省大量的連線時間。
5、節省空間
redis是一個記憶體資料庫,所有的資料都儲存在記憶體中,所以如何優化儲存,減少記憶體空間的佔用對成本控制來說是一個重要的話題。
1)精簡鍵名和鍵值
精簡鍵名和鍵值是最直觀的減少記憶體佔用的方式。當然精簡鍵名也要把握好一個度,不能為了減少記憶體佔用而使用一些不易理解的鍵名,這樣既不易維護也容易造成鍵名重複。再比如儲存性別的male和female,我們可以用m和f表示,當然也可以用0和1表示性別。
2)內部編碼優化