当前位置: 首页 > 编程日记 > 正文

Redis消息通知系统的实现

Redis消息通知系统的实现

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

==========================================
Web应用中的轻量级消息队列
原文地址:http://hi.baidu.com/thinkinginlamp/blog/item/27a18202578f3d054bfb511f.html
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语法来操作消息队列,也是一个不错的选择。

相关文章:

用 Python 实现答题卡识别!

作者 | 棒子胡豆来源丨CSDN博客答题卡素材图片&#xff1a;思路读入图片&#xff0c;做一些预处理工作。进行轮廓检测&#xff0c;然后找到该图片最大的轮廓&#xff0c;就是答题卡部分。进行透视变换&#xff0c;以去除除答题卡外的多余部分&#xff0c;并且可以对答题卡进行校…

Confluence 6 计划任务

管理员控制台能够允许你对 Confluence 运行的计划任务进行计划的调整&#xff0c;这些计划任务将会按照你的调整按时执行。可以按照计划执行的任务如下&#xff1a; Confluence 站点备份存储优化任务&#xff0c;清理 Confluence 的临时目录中的文件和缓存索引优化任务&#xf…

PHP共享内存段

在asp.net和java中都有共享内存&#xff0c;php除了可以使用Memcached等方式变通以外其实php也是支持共享内存的&#xff01;需要安装扩展shmop 找到php安装源文件目录# cd /usr/local/php-5.4.0/ext/shmop # /usr/local/php/bin/phpize # ./configure --with-php-config/usr/l…

马尔科夫随机场的基本概念

1、随机过程&#xff1a; 描写叙述某个空间上粒子的随机运动过程的一种方法。它是一连串随机事件动态关系的定量描写叙述。随机过程与其他数学分支&#xff0c;如微分方程、复变函数等有密切联系。是自然科学、project科学及社会科学等领域研究随机现象的重要工具。 2、马尔科夫…

从事了两年 AI 研究,我学到了什么?

作者 | Tom Silver译者 | 弯月出品 | CSDN我从事人工智能研究的工作已经有两年了&#xff0c;有朋友问我都学到了什么&#xff0c;所以我想借本文分享一些迄今为止积累的经验教训。我将在本文中分享一些常见的经历&#xff0c;还会讨论相对具体的人工智能行业技巧。希望对大家能…

Windows server 2008普通用户不能远程登录问题

1、查登录权限 如果文件服务器没有为用户授权&#xff0c;那么用户自然就不能远程登录服务器系统了&#xff0c;为此笔者决定先仔细检查一下文件服务器系统是否为自己使用的登录账号&#xff0c;授予了远程登录权限。在进行这种检查时&#xff0c;笔者先是在文件服务器本地以系…

面向小白的最全 Python 可视化教程,超全的!

作者 | 俊欣来源丨关于数据分析与可视化今天小编总结归纳了若干个常用的可视化图表&#xff0c;并且通过调用plotly、matplotlib、altair、bokeh和seaborn等模块来分别绘制这些常用的可视化图表&#xff0c;最后无论是绘制可视化的代码&#xff0c;还是会指出来的结果都会通过调…

Atitit.文件搜索工具 attilax 总结

Atitit.文件搜索工具 attilax 总结 1. 指定目录按照体积大小精确搜索1 1.1. File Seeker 4.5 版本的可以&#xff0c;3.5版本的不行。。1 2. 按照文件内容搜索1 2.1. File Seeker2 2.2. Notepad2 2.3. FileLocator Pro 是一款专业的文件搜索软件&#xff0c;2 2.4. 百度硬盘搜索…

ulimit -SHn 65535 含义

linux下用ulimit设置连接数最大值&#xff0c;默认是1024.在高负载下要设置为更高&#xff0c;但最高只能为65535. ulimit只能做临时修改&#xff0c;重启后失效。可以加入ulimit -SHn 65535到 /etc/rc.local 每次启动启用。终极解除 Linux 系统的最大进程数和最大文件打开数限…

5.7-基于Binlog+Position的复制搭建

基本环境 MasterSlaveMySQL版本MySQL-5.7.16-X86_64MySQL-5.7.16-X86_64IP192.168.56.156192.168.56.157Port33063306数据库环境的部署&#xff1a;两边安装好相同的数据库软件&#xff0c;初始化&#xff0c;可以启动起来。检查事项&#xff1a;两边防火墙是否开启&#xff0c…

PHP消息队列httpsqs安装与使用无错版

项目网址&#xff1a;http://code.google.com/p/httpsqs/使用文档&#xff1a;http://blog.s135.com/httpsqs/ 说明&#xff1a;由于需要安装的东西有些多&#xff0c;原文可能写的有些简略&#xff0c;所以适当补充了1.安装libevent-2.0.12-stable.tar.gzwget http://httpsqs.…

Windows Server 2012 系统群集

Windows Server 2012 系统群集本次将测试Windows Server 2012 系统群集功能。实验环境&#xff1a;4台服务器都为Windows Server 2012 DataCenter操作系统SRV2012服务器安装iSCSI目标服务器角色并配置2块虚拟磁盘给两台群集服务器共享使用。群集服务器安装群集功能&#xff0c;…

Python 中最强大的错误重试库

作者 | 费弗里来源丨Python大数据分析1 简介我们在编写程序尤其是与网络请求相关的程序&#xff0c;如调用web接口、运行网络爬虫等任务时&#xff0c;经常会遇到一些偶然发生的请求失败的状况&#xff0c;这种时候如果我们仅仅简单的捕捉错误然后跳过对应任务&#xff0c;肯定…

Spring Cloud云架构 - SSO单点登录之OAuth2.0登录流程(2)

上一篇是站在巨人的肩膀上去研究OAuth2.0&#xff0c;也是为了快速帮助大家认识OAuth2.0&#xff0c;闲话少说&#xff0c;我根据框架中OAuth2.0的使用总结&#xff0c;画了一个简单的流程图&#xff08;根据用户名密码实现OAuth2.0的登录认证&#xff09;&#xff1a; 上面的图…

php的POSIX 函数以及进程测试

参考&#xff1a;http://cn.php.net/manual/zh/ref.posix.php <?phpecho posix_getpid(); //8805sleep(10);?>再用 #ps -ax这个时候如果多开开个浏览器请求&#xff0c;就会发现Apache自动增加了几个新的进程我们发现并非一直请求同一个进程重启apache # /usr/local/ap…

CAS (10) —— JBoss EAP 6.4下部署CAS时出现错误exception.message=Error decoding flow execution的解决办法...

CAS (10) —— JBoss EAP 6.4下部署CAS时出现错误exception.messageError decoding flow execution的解决办法 jboss版本: jboss-eap-6.4-CVE-2015-7501 jdk版本: 1.7.0_79 cas版本: cas 4.1.3 参考来源: Nabble: exception.messageErrordecodingflowexecution Nabble: Caused …

昇思MindSpore1.6发布 AI开发者体验再升级

智能时代&#xff0c;AI技术正在发挥至关重要的作用&#xff0c;而开源的深度学习框架不仅能够降低AI开发者的门槛&#xff0c;而且能够极大节省成本与时间&#xff0c;成为创新的重要推手。2022年3月26-27日&#xff0c;昇思MindSpore TechDay活动线上成功举办&#xff0c;包含…

深入解析:TRUNCATE TABLE 的内部原理解析与恢复思路

摘要 众所周知&#xff0c;truncate table 是一种快速清空表内数据的一种方式&#xff0c;与 delete 方式不同&#xff0c;truncate 只产生非常少的 redo 和 undo&#xff0c;就实现了清空表数据并降低表 HWM 的功能。本文主要围绕 truncate table 的实现原理和 truncate table…

Linux exec与重定向

exec和source都属于bash内部命令&#xff08;builtins commands&#xff09;&#xff0c;在bash下输入man exec或man source可以查看所有的内部命令信息。 bash shell的命令分为两类&#xff1a;外部命令和内部命令。外部命令是通过系统调用或独立的程序实现的&#xff0c;如se…

俄罗斯 Android 系统受限,或将转用 HarmonyOS!

整理 | 郑丽媛出品 | CSDN近一个月来&#xff0c;受当前局势影响&#xff0c;部分底层工具、基础软件、开源项目已相继宣布在俄罗斯停服&#xff0c;期间不少人因此担忧&#xff1a;同出自美国且占据极大智能手机市场的 Android 和 iOS 是否会趁机“作乱”&#xff1f;结果&…

Ios应用网络安全之https

戴维营教育原创文章&#xff0c;转载请注明出处。我们的梦想是做最好的iOS开发培训&#xff01;iOS应用网络安全之HTTPS1. HTTPS/SSL的基本原理安全套接字层 (Secure Socket Layer, SSL) 是用来实现互联网安全通信的最普遍的标准。Web 应用程序使用 HTTPS&#xff08;基于 SSL …

云原生应用的10大关键属性

2019独角兽企业重金招聘Python工程师标准>>> “云原生”是用于描述基于容器的环境的术语&#xff0c;而Kubernetes是一个运行云原生应用程序工作负载的理想平台。 开发人员在设计云原生应用程序时&#xff0c;一定要牢记本文内这10个关键属性&#xff01; “云原生&…

grep 正则表达式及选项以及注意

说明&#xff1a;在原文基础上稍作了修改grep命令简介&#xff1a; 在ex编辑器&#xff08;我没用过&#xff09;中&#xff0c;启动ex编辑器后要查找某个字符串时&#xff0c;在ex的命令提示符后键入::/pattern/p:/g/pattern/pgrep这个名字就由来如此。其中p的含义是print&…

iOS_25彩票_幸运转盘

终于效果图: 各个view的关系图: 背景圆盘(须要扣图处理)LuckyBaseBackground.png 盖在背景圆盘上面的转盘 LuckyRotateWheel.png 代表一个星座或生肖的button背景图片 要创建12个,并以最下方中点为锚点进行旋转 对背景圆盘进行扣图,并在其上面盖上转盘图片的核心代码 在自己定义…

Python 自动化办公之 Excel 对比工具

作者 | 周萝卜来源丨萝卜大杂烩今天我们继续分享真实的自动化办公案例&#xff0c;希望各位 Python 爱好者能够从中得到些许启发&#xff0c;在自己的工作生活中更多的应用 Python&#xff0c;使得工作事半功倍&#xff01;需求由于工作当中经常需要对比前后两个 Excel 文件&am…

jQuery简单实现iframe的高度根据页面内容自适应的方法(转)

本文实例讲述了jQuery简单实现iframe的高度根据页面内容自适应的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;方式1&#xff1a;//注意&#xff1a;下面的代码是放在和iframe同一个页面中调用 $("#iframeId").load(function () {var mainheight $(t…

linux wc 命令简介

此wc命令不是让大家没有食欲的地方。而是linux下一个简单的小命令。NAMEwc — word, line, character, and byte countSYNOPSISwc [-clmw] [file ...]下面让我们来简单的看一下其支持的参数及其代表的含义。 参数及含义 参数含义-c显示文件的Bytes数(字节数)-l将每个文件的行数…

这个插件竟打通了Python和Excel,还能自动生成代码!

作者 | 云朵君来源丨数据STUDIO加载一个Jupyter插件后&#xff0c;无需写代码就能做数据分析&#xff0c;还帮你生成相应代码&#xff1f;没错&#xff0c;只需要加载这个名为Mito的小工具包&#xff0c;用Python做数据分析&#xff0c;变得和用Excel一样简单&#xff1a;介绍以…

集合list set Map问题

2019独角兽企业重金招聘Python工程师标准>>> ####集合list set Map的个人理解 首先集合说的对一类数据的存储容器&#xff0c;对象都是引用类型并不是基本数据类型 collection 接口 list和set都需实现它 collections 抽象了一些集合的基本功能&#xff0c;reverse s…

python学习第四课

#!/user/bin/env python#-*-coding:utf-8-*-# 一、字符串魔法# &#xff08;1&#xff09;.isalpha()是否是字母或汉字。# 例&#xff1a;# a"张san22"# b"张三lisi"# va.isalpha()# v1b.isalpha()# print(v)# print(v1)# 因为a里含有数字&#xff0c;结果…