[转][转]Redis消息通知系统的实现

标签: redis 消息 系统 | 发表时间:2012-03-17 21:49 | 作者:heiyeshuwu
出处:http://blog.csdn.net/heiyeshuwu


作者:老王
链接:http://huoding.com/2012/02/29/146


最近忙着用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);
}

?>

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实现消息通知系统的一些情况,有兴趣的不妨一起看看。


作者:heiyeshuwu 发表于2012-3-17 21:49:01 原文链接
阅读:45 评论:0 查看评论

相关 [redis 消息 系统] 推荐:

[转][转]Redis消息通知系统的实现

- - heiyeluren的Blog
链接:http://huoding.com/2012/02/29/146. 最近忙着用Redis实现一个消息通知系统,今天大概总结了一下技术细节,其中演示代码如果没有特殊说明,使用的都是 PhpRedis扩展来实现的. 比如要推送一条全局消息,如果真的给所有用户都推送一遍的话,那么会占用很大的内存,实际上不管粘性有多高的产品,活跃用户同全部用户比起来,都会小很多,所以如果只处理登录用户的话,那么至少在内存消耗上是相当划算的,至于未登录用户,可以推迟到用户下次登录时再处理,如果用户一直不登录,就一了百了了.

Redis系统性介绍

- gnawux - NoSQLFan
虽然Redis已经很火了,相信还是有很多同学对Redis只是有所听闻或者了解并不全面,下面是一个比较系统的Redis介绍,对Redis的特性及各种数据类型及操作进行了介绍. 是一个很不错的Redis入门教程. REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统.

redis作为消息队列的使用

- - ITeye博客
在redis支持的数据结构中,有一个是集合list. 对List的操作常见的有lpush  lrange等. 在这种常见的操作时,我们是把集合当做典型意义上的‘集合’来使用的. 往往容易被忽视的是List作为“队列”的使用情况. 反编译redis的jar包,会发现:.  pop意为“弹”,是队列里的取出元素.

用 Redis 轻松实现秒杀系统

- - 博客 - 伯乐在线
曾经被问过好多次怎样实现秒杀系统的问题. 昨天又在CSDN架构师微信群被问到了. 因此这里把我设想的实现秒杀系统的价格设计分享出来. 秒杀系统,是典型的短时大量突发访问类问题. 对这类问题,有三种优化性能的思路:. 写入内存而不是写入硬盘、异步处理而不是同步处理、分布式处理. 用上这三招,不论秒杀时负载多大,都能轻松应对.

用redis实现支持优先级的消息队列

- - 企业架构 - ITeye博客
用redis实现支持优先级的消息队列. 系统中引入消息队列机制是对系统一个非常大的改善. 例如一个web系统中,用户做了某项操作后需要发送邮件通知到用户邮箱中. 你可以使用同步方式让用户等待邮件发送完成后反馈给用户,但是这样可能会因为网络的不确定性造成用户长时间的等待从而影响用户体验. 有些场景下是不可能使用同步方式等待完成的,那些需要后台花费大量时间的操作.

基于Redis构建系统的经验和教训

- - idea's blog
Redis 是一个非常快速和强大的 Key-Value 存储(持久化)系统, 相对于一般的 NoSQL 存储系统, 它最大的特点是支持丰富的数据结构. 特别是其 zset(sorted set)数据结构, 堪称表达能力最强的结构之一(其它强大的数据结构如 sorted hashmap), 可以直接地表达业务逻辑..

基于Redis的限流系统的设计

- -
基于Redis的限流系统的设计,主要会谈及限流系统中. 限流策略这个功能的设计;在实现方面,算法使用的是. 令牌桶算法来,访问Redis使用lua脚本. rate limiting is used to control the rate of traffic sent or received by a network interface controller and is used to prevent DoS attacks.

常见开源消息系统

- - Web 开发 : 从后端到前端
消息系统的作用:异步处理、削减峰值、减少组件之间的耦合. 选择消息系统根据业务需要需要考虑以下几个方面:. 其他,如消息丢失和重复的处理. 类似 MEMCACHE 的协议. 1、2 是不错的可选开源组件:. Kafka/MetaQ: 广泛用于 Linkedin 内部 (类似有 Java 版本的国产 MetaQ).

分布式消息系统:Kafka

- - 标点符
Kafka是分布式发布-订阅消息系统. 它最初由LinkedIn公司开发,之后成为Apache项目的一部分. Kafka是一个分布式的,可划分的,冗余备份的持久性的日志服务. 在大数据系统中,常常会碰到一个问题,整个大数据是由各个子系统组成,数据需要在各个子系统中高性能,低延迟的不停流转. 传统的企业消息系统并不是非常适合大规模的数据处理.

kafka分布式消息系统

- - CSDN博客云计算推荐文章
Kafka[1]是linkedin用于日志处理的分布式消息队列,linkedin的日志数据容量大,但对可靠性要求不高,其日志数据主要包括用户行为(登录、浏览、点击、分享、喜欢)以及系统运行日志(CPU、内存、磁盘、网络、系统及进程状态). 当前很多的消息队列服务提供可靠交付保证,并默认是即时消费(不适合离线).