Redis消息通知系统的实现

PHP中文网
发布: 2017-03-21 16:41:05
原创
2610人浏览过

最近忙着用redis实现一个消息通知系统,今天大概总结了一下技术细节,其中演示代码如果没有特殊说明,使用的都是phpredis扩展来实现的。 内存 比如要推送一条全局消息,如果真的给所有用户都推送一遍的话,那么会占用很大的内存,实际上不管粘性有多高的产品

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

内存

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

队列

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

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

connect('/tmp/redis.sock');
$redis->lPush('usr',);
while ($usr = $redis->rPop('usr')) {
    var_dump($usr);
}
?>
登录后复制

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

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

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实现队列了,代码如下:

connect('/tmp/redis.sock');
$redis->zAdd('msg',,);
while ($msg = $redis->zRevPop('msg')) {
    var_dump($msg);
}
?>
登录后复制

推拉

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

速度

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

<?php
for ($msgid = 1; $msgid sAdd('usr::msg', $msgid);
}
?>
登录后复制

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

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

<?php
for ($i = 1; $i multi(Redis::PIPELINE);
    for ($j = 1; $j sAdd('usr::msg', $msgid);
    }
    $redis->exec();
}
?>
登录后复制

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

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

查询

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

先插入三条消息,其

国微CMS企业外网方案(原PHP168  S系列)
国微CMS企业外网方案(原PHP168 S系列)

国微CMS企业方案基于“核心+系统+模块+插件”的架构体系,拓展性良好。能非常方便站长及企业搭建企业信息平台。 手机短信体系平台A、 每个售后问题回复,客户均可收到快捷通知短信。B、 每个货物发送,均有一个快捷短信息发给收货方。C、 每个客户均可按实际需求收到手机短信回复与问候。D、每个订单申请都会有一个快捷短信回复。E、每个代理商申请代理均可得到短信回复。

国微CMS企业外网方案(原PHP168  S系列) 0
查看详情 国微CMS企业外网方案(原PHP168  S系列)
redis> HMSET msg:1 title title1 content content1
redis> HMSET msg:2 title title2 content content2
redis> HMSET msg:3 title title3 content content3
登录后复制

再把这三条消息发送给某个用户,其

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

相关文章:

HTML 5的消息通知机制

web消息通知系统设计问题

基于HTML5 Notifications API的消息通知插件

相关标签:
最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号