Redis訊息通知系統的實現

2021-06-05 18:03:37 字數 4555 閱讀 7137

posted on

2012-02-29

by 老王

最近忙著用redis實現乙個訊息通知系統,今天大概總結了一下技術細節,其中演示**如果沒有特殊說明,使用的都是phpredis擴充套件來實現的。

比如要推送一條全域性訊息,如果真的給所有使用者都推送一遍的話,那麼會占用很大的記憶體,實際上不管粘性有多高的產品,活躍使用者同全部使用者比起來,都會小很多,所以如果只處理登入使用者的話,那麼至少在記憶體消耗上是相當划算的,至於未登入使用者,可以推遲到使用者下次登入時再處理,如果使用者一直不登入,就一了百了了。

當大量使用者同時登入的時候,如果全部都即時處理,那麼很容易就崩潰了,此時可以使用乙個佇列來儲存待處理的登入使用者,如此一來頂多是反應慢點,但不會崩潰。

redis的list資料型別可以很自然的建立乙個佇列,**如下:

<?php

$redis = new redis;

$redis->connect('/tmp/redis.sock');

$redis->lpush('usr', );

while ($usr = $redis->rpop('usr'))

?>

出於類似的原因,我們還需要乙個佇列來儲存待處理的訊息。當然也可以使用list來實現,但list只能按照插入的先後順序實現類似fifo或lifo形式的佇列,然而訊息實際上是有優先順序的:比如說個人訊息優先順序高,全域性訊息優先順序低。此時可以使用zset來實現,它裡面分數的概念很自然的實現了優先順序。

不過zset沒有原生的pop操作,所以我們需要模擬實現,**如下:

<?php

class redisclient extends redis

public function zrevpop($zset)

private function zsetpop($zset, $position)

if ($this->multi()->zrem($zset, $element[0])->exec())

return $this->zsetpop($zset, $position);

}}?>

模擬實現了pop操作後,我們就可以使用zset實現佇列了,**如下:

<?php

$redis = new redisclient;

$redis->connect('/tmp/redis.sock');

$redis->zadd('msg', , );

while ($msg = $redis->zrevpop('msg'))

?>

以前微博架構中推拉選擇的問題已經被大家討論過很多次了。實際上訊息通知系統和微博差不多,也存在推拉選擇的問題,同樣答案也是類似的,那就是應該推拉結合。具體點說:在登陸使用者獲取訊息的時候,就是乙個拉訊息的過程;在把訊息傳送給登陸使用者的時候,就是乙個推訊息的過程。

假設要推送一百萬條訊息的話,那麼最直白的實現就是不斷的插入,**如下:

<?php

for ($msgid = 1; $msgid <= 1000000; $msgid++)

?>

redis的速度是很快的,但是借助pipeline,會更快,**如下:

<?php

for ($i = 1; $i <= 100; $i++)

$redis->exec();

}?>

說明:所謂pipeline,就是省略了無謂的折返跑,把命令打包給服務端統一處理。

前後兩段**在我的測試裡,使用pipeline的速度大概是不使用pipeline的十倍。

我們用redis命令列來演示一下使用者是如何查詢訊息的。

先插入三條訊息,其分別是1,2,3:

redis> hmset msg:1 title title1 content content1

redis> hmset msg:2 title title2 content content2

redis> hmset msg:3 title title3 content content3

再把這三條訊息傳送給某個使用者,其是123:

redis> sadd usr:123:msg 1

redis> sadd usr:123:msg 2

redis> sadd usr:123:msg 3

此時如果簡單查詢使用者有哪些訊息的話,無疑只能查到一些:

redis> smembers usr:123:msg

1) "1"

2) "2"

3) "3"

如果還需要用程式根據再來一次查詢無疑有點低效,好在redis內建的sort命令可以達到事半功倍的效果,實際上它類似於sql中的join:

redis> sort usr:123:msg get msg:*->title

1) "title1"

2) "title2"

3) "title3"

redis> sort usr:123:msg get msg:*->content

1) "content1"

2) "content2"

3) "content3"

sort的缺點是它只能get出字串型別的資料,如果你想要多個資料,就要多次get:

redis> sort usr:123:msg get msg:*->title get msg:*->content

1) "title1"

2) "content1"

3) "title2"

4) "content2"

5) "title3"

6) "content3"

很多情況下這顯得不夠靈活,好在我們可以採用其他一些方法平衡一下利弊,比如說新加乙個字段,冗餘儲存完整訊息的序列化,接著只get這個欄位就ok了。

實際暴露查詢介面的時候,不會使用php等程式來封裝,因為那會成倍降低rps,推薦使用webdis,它是乙個redis的web**,效率沒得說。

最近tumblr發表了一篇類似的文章:staircar: redis-powered notifications,介紹了他們使用redis實現訊息通知系統的一些情況,有興趣的不妨一起看看。

****************************************==

web應用中的輕量級訊息佇列

web應用中為什麼會需要訊息佇列?主要原因是由於在高併發環境下,由於來不及同步處理,請求往往會發生堵塞,比如說,大量的insert,update之類的請求同時到達mysql,直接導致無數的行鎖表鎖,甚至最後請求會堆積過多,從而觸發too many connections錯誤。通過使用訊息佇列,我們可以非同步處理請求,從而緩解系統的壓力。在web2.0的時代,高併發的情況越來越常見,從而使訊息佇列有成為居家必備的趨勢,相應的也湧現出了很多實現方案,像twitter以前就使用

rabbitmq實現訊息佇列服務,現在又轉而使用

kestrel來實現訊息佇列服務,此外還有很多其他的選擇,比如說:

activemq,

zeromq等。

上述訊息佇列的軟體中,大多為了實現amqp,stomp,xmpp之類的協議,變得極其重量級,但在很多web應用中的實際情況是:我們只是想找到乙個緩解高併發請求的解決方案,不需要雜七雜八的功能,乙個輕量級的訊息佇列實現方式才是我們真正需要的。

第一感覺是能不能使用

memcached來實現訊息佇列?稍加考慮後就會發現它不合適,因為memcached僅僅支援鍵值方式的操作,沒有排序之類的功能,所以如果要用它來實現訊息佇列,則必須自己通過某個鍵來儲存陣列形式的佇列,不過這樣的話,在操作佇列的時候很容易丟失資料,比如說我們要新增乙個訊息,則需先取出現有佇列,然後把訊息儲存到佇列尾部,最後儲存佇列,單純使用memcached的話,由於我們無法保證整個過程的原子性,所以當處理若干個併發請求時,各個請求間可能會互相覆蓋,丟失資料就在所難免(

新的memcached擴充套件一定程度上能緩解這個問題)。另外,memcached只是記憶體鍵值快取而已,一旦宕機,資料就消失了。

memcacheq的出現解決了上面的問題,它在memcached的基礎上實現了訊息佇列,以php客戶端為例:

訊息從尾部入棧:memcache_set

訊息從頭部出棧:memcache_get

memcacheq依附於memcached之上,所以你可以通過現有的memcached工具來操作它,這無疑是它的一大優勢,但它也有乙個很大的缺點,那就是memcacheq本身的開發維護似乎並不活躍,如果遇到問題的話,你很可能需要自己動手解決。

目前看來,我更推薦下面這種解決方案,那就是

redis,如果不了解,可以參考我以前的

文章,表面上看,redis和memcached差不多,也是鍵值操作,但是redis本身實現了

list,相關操作也可以保證是原子的,所以可以很自然的通過list來實現訊息佇列:

訊息從尾部進佇列:rpush

訊息從頭部出佇列:lpop

redis本身雖然是乙個新專案,但很有朝氣,開發維護也很活躍,如果你的下乙個web應用裡需要使用輕量級的訊息佇列,不妨使用它,順便說一句,redis裡還有set結構,可以用來實現乙個高效能的tag系統。

此外,還有不少其他的選擇可供嘗試,比如說mysql第三方的

q4m引擎,通過擴充套件sql語法來操作訊息佇列,也是乙個不錯的選擇。

Redis訊息通知系統的實現

最近忙著用redis實現乙個訊息通知系統,今天大概總結了一下技術細節,其中演示 如果沒有特殊說明,使用的都是phpredis擴充套件來實現的。比如要推送一條全域性訊息,如果真的給所有使用者都推送一遍的話,那麼會占用很大的記憶體,實際上不管粘性有多高的產品,活躍使用者同全部使用者比起來,都會小很多,所...

Redis訊息通知系統的實現

最近忙著用redis實現乙個訊息通知系統,今天大概總結了一下技術細節,其中演示 如果沒有特殊說明,使用的都是phpredis擴充套件來實現的。比如要推送一條全域性訊息,如果真的給所有使用者都推送一遍的話,那麼會占用很大的記憶體,實際上不管粘性有多高的產品,活躍使用者同全部使用者比起來,都會小很多,所...

Redis訊息通知系統的實現

posted on 2012 02 29 by 老王 最近忙著用redis實現乙個訊息通知系統,今天大概總結了一下技術細節,其中演示 如果沒有特殊說明,使用的都是phpredis擴充套件來實現的。比如要推送一條全域性訊息,如果真的給所有使用者都推送一遍的話,那麼會占用很大的記憶體,實際上不管粘性有多...