Redis訊息通知系統的實現

2021-07-04 02:49:04 字數 3017 閱讀 8393

最近忙著用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++)

?>

說明:這裡我使用了set資料型別,當然你也可以視需求換成list或者zset。

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實現訊息通知系統的一些情況,有興趣的不妨一起看看。

Redis訊息通知系統的實現

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

Redis訊息通知系統的實現

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

Redis訊息通知系統的實現

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