1. 程式人生 > >Redis高階進階(二)

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)內部編碼優化