Redis消息通知系统的实现

Posted on 2012-02-29

最近忙着用Redis实现一个消息通知系统,今天大概总结了一下技术细节,其中演示代码如果没有特殊说明,使用的都是PhpRedis扩展来实现的。

内存

比如要推送一条全局消息,如果真的给所有用户都推送一遍的话,那么会占用很大的内存,实际上不管粘性有多高的产品,活跃用户同全部用户比起来,都会 小很多,所以如果只处理登录用户的话,那么至少在内存消耗上是相当划算的,至于未登录用户,可以推迟到用户下次登录时再处理,如果用户一直不登录,就一了 百了了。

队列

当大量用户同时登录的时候,如果全部都即时处理,那么很容易就崩溃了,此时可以使用一个队列来保存待处理的登录用户,如此一来顶多是反应慢点,但不会崩溃。

Redis的LIST数据类型可以很自然的创建一个队列,代码如下:

<?php

$redis = new Redis;
$redis->connect('/tmp/redis.sock'); $redis->lPush('usr', <USRID>); while ($usr = $redis->rPop('usr')) {
var_dump($usr);
} ?>

出于类似的原因,我们还需要一个队列来保存待处理的消息。当然也可以使用LIST来实现,但LIST只能按照插入的先后顺序实现类似FIFO或LIFO形式的队列,然而消息实际上是有优先级的:比如说个人消息优先级高,全局消息优先级低。此时可以使用ZSET来实现,它里面分数的概念很自然的实现了优先级。

不过ZSET没有原生的POP操作,所以我们需要模拟实现,代码如下:

<?php

class RedisClient extends Redis
{
const POSITION_FIRST = 0;
const POSITION_LAST = -1; public function zPop($zset)
{
return $this->zsetPop($zset, self::POSITION_FIRST);
} public function zRevPop($zset)
{
return $this->zsetPop($zset, self::POSITION_LAST);
} private function zsetPop($zset, $position)
{
$this->watch($zset); $element = $this->zRange($zset, $position, $position); if (!isset($element[0])) {
return false;
} if ($this->multi()->zRem($zset, $element[0])->exec()) {
return $element[0];
} return $this->zsetPop($zset, $position);
}
} ?>

模拟实现了POP操作后,我们就可以使用ZSET实现队列了,代码如下:

<?php

$redis = new RedisClient;
$redis->connect('/tmp/redis.sock'); $redis->zAdd('msg', <PRIORITY>, <MSGID>); while ($msg = $redis->zRevPop('msg')) {
var_dump($msg);
} ?>

推拉

以前微博架构中推拉选择的问题已经被大家讨论过很多次了。实际上消息通知系统和微博差不多,也存在推拉选择的问题,同样答案也是类似的,那就是应该 推拉结合。具体点说:在登陆用户获取消息的时候,就是一个拉消息的过程;在把消息发送给登陆用户的时候,就是一个推消息的过程。

速度

假设要推送一百万条消息的话,那么最直白的实现就是不断的插入,代码如下:

<?php

for ($msgid = 1; $msgid <= 1000000; $msgid++) {
$redis->sAdd('usr:<USRID>:msg', $msgid);
} ?>

说明:这里我使用了SET数据类型,当然你也可以视需求换成LIST或者ZSET。

Redis的速度是很快的,但是借助PIPELINE,会更快,代码如下:

<?php

for ($i = 1; $i <= 100; $i++) {
$redis->multi(Redis::PIPELINE);
for ($j = 1; $j <= 10000; $j++) {
$msgid = ($i - 1) * 10000 + $j;
$redis->sAdd('usr:<USRID>:msg', $msgid);
}
$redis->exec();
} ?>

说明:所谓PIPELINE,就是省略了无谓的折返跑,把命令打包给服务端统一处理。

前后两段代码在我的测试里,使用PIPELINE的速度大概是不使用PIPELINE的十倍。

查询

我们用Redis命令行来演示一下用户是如何查询消息的。

先插入三条消息,其<MSGID>分别是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

再把这三条消息发送给某个用户,其<USRID>是123:

redis> SADD usr:123:msg 1
redis> SADD usr:123:msg 2
redis> SADD usr:123:msg 3

此时如果简单查询用户有哪些消息的话,无疑只能查到一些<MSGID>:

redis> SMEMBERS usr:123:msg
1) "1"
2) "2"
3) "3"

如果还需要用程序根据<MSGID>再来一次查询无疑有点低效,好在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实现消息通知系统的一些情况,有兴趣的不妨一起看看。

This entry was posted in Technical and tagged Performance, Redis by 老王. Bookmark the permalink.

redis——队列的更多相关文章

  1. [bigdata] 使用Redis队列来实现与机器无关的Job提交与执行 (python实现)

    用例场景: 定时从远程多台机器上下载文件存入HDFS中.一开始采用shell 一对一的方式实现,但对于由于网络或者其他原因造成下载失败的任务无法进行重试,且如果某台agent机器down机,将导致它对 ...

  2. 我心中的核心组件~MSMQ与Redis队列

    回到目录 这个文章其实是我心中的核心组件的第七回,确实在时间上有些滞后了,但内容并不滞后!本文MSMQ只是个引题,我确实不太想说它,它是微软自己集成的一套消息队列,寄宿在Window服务里,稳定性十在 ...

  3. c#之Redis队列在邮件提醒中的应用

    场景 有这样一个场景,一个邮件提醒的windows服务,获取所有开启邮件提醒的用户,循环获取这些用户的邮件,发送一条服务号消息.但问题来了,用户比较少的情况下,轮询一遍时间还能忍受,如果用户多了,那用 ...

  4. c#之Redis队列

    摘要 这两天一直在考虑redis队列:一个生产者,多个消费者的情况,这里弄了一个demo进行测试. 一个例子 关于如何引用Redisclient 可以参考之前的这篇文章:c#之Redis实践list, ...

  5. redis队列的实现

    redis中文官网:http://www.redis.cn/ 关于redis队列的实现方式有两种: 1.生产者消费者模式. 2.发布者订阅者模式. 详解: 1.生产者消费者模式. 普通版本: 比如一个 ...

  6. (3)redis队列功能

    Redis队列功能介绍 List 常用命令: Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用 Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 Brpoplpus ...

  7. 转载:【高并发简单解决方案 | 靠谱崔小拽 】redis队列缓存 + mysql 批量入库 + php离线整合

    需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

  8. redis 队列缓存 + mysql 批量入库 + php 离线整合

    问题分析 思考:应用网站架构的衍化过程中,应用最新的框架和工具技术固然是最优选择:但是,如果能在现有的框架的基础上提出简单可依赖的解决方案,未尝不是一种提升自我的尝试. 解决: 问题一:要求日志最好入 ...

  9. PHP电商订单自动确认收货redis队列

    一.场景 之前做的电商平台,用户在收到货之后,大部分都不会主动的点击确认收货,导致给商家结款的时候,商家各种投诉,于是就根据需求,要做一个订单在发货之后的x天自动确认收货.所谓的订单自动确认收货,就是 ...

  10. 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合

    需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

随机推荐

  1. Spring Data Jpa 使用@Query标注自定义查询语句

    https://blog.csdn.net/daniel7443/article/details/51159865 https://blog.csdn.net/pp_fzp/article/detai ...

  2. 【知识笔记】ASP.NET报错问题

    一.前端asp.net在远程部署到IIS时报错:Web 部署任务失败,在远程计算机上处理请求时出错 服务器在处理请求时遇到问题.请与服务器管理员联系以了解详细信息((2017/6/16 16:22:4 ...

  3. scanner 在java中的输入

    ************************************************************************************** 和C++的输入很像

  4. dbt macro 说明

    macro是SQL的片段,可以像模型中的函数一样调用.macro可以在模型之间重复使用SQL,以符合DRY(不要重复自己)的工程原理. 此外,共享包可以公开您可以在自己的dbt项目中使用的macro. ...

  5. baidu手机浏览器安卓4.5版公布:由于快,所以爱

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxNDUyMzk4OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...

  6. 实战:mysql检查物理磁盘中的二进制日志文件是否有丢失

    版权声明:日常札记,乐于分享 https://blog.csdn.net/yangzhawen/article/details/32103555 场景:有时候由于磁盘损坏或人为原因错误删除了磁盘中的二 ...

  7. MySQL: ON DUPLICATE KEY UPDATE 用法

    使用该语法可在插入记录的时候先判断记录是否存在,如果不存在则插入,否则更新,很方便,无需执行两条SQL INSERT INTO osc_visit_stats(stat_date,type,id,vi ...

  8. Spring Cloud 入门 之 Config 篇(六)

    原文地址:Spring Cloud 入门 之 Config 篇(六) 博客地址:http://www.extlight.com 一.前言 随着业务的扩展,为了方便开发和维护项目,我们通常会将大项目拆分 ...

  9. Qt5布局管理(一)——QSplitter分割窗口类

    转载:LeeHDsniper 概述 本文首先通过三个实例分别介绍Qt5的分割窗口QSplitter类.停靠窗口QDockWidget类.堆栈窗体QStackedWidget类,然后介绍布局管理器的使用 ...

  10. 第4章 NumPy基础

    NumPy是高性能科学计算和数据分析的基础包. 主要功能: 1.ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组. 2.用于对整组数据进行快速运算的标准数学函数(无需编写循 ...