文档章节

转:Redis消息队列的若干实现方式

fzxu_05
 fzxu_05
发布于 2013/03/04 15:57
字数 1298
阅读 6.3K
收藏 7

#程序员薪资揭榜#你做程序员几年了?月薪多少?发量还在么?>>>

  微信号:neihanrukou

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

本文转载自网络

fzxu_05
粉丝 44
博文 165
码字总数 84201
作品 0
朝阳
程序员
私信 提问
加载中

评论(0)

RabbitMQ 延迟队列,消息延迟推送

作者: 海向 出处:https://www.cnblogs.com/haixiang/p/10966985.html 应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货。在我们签收商...

海向
2019/06/03
0
0
redis实现消息队列的若干疑惑

相关疑惑 1.发布者如何知道订阅者是否知道消息 2.如果只有部分订阅者接收到消息,发布者应该如何处理 redis消息队列 利用redis的pub/sub实现消息队列,当然也可以使用redis的队列实现,只不是...

OSC屠夫
2015/07/28
1.1K
0
Redis入门到高可用(一)——初识Redis

一、Redis是什么 * 开源 * 基于键值的存储服务系统 * 支持多种数据结构 * 高性能,功能丰富 二、Redis特性 ♦️ 概述 * 速度快 * 支持持久化 * 支持多种数据结构 * 支持多种编辑语言 * 功能丰...

osc_r2doglc9
2018/05/15
1
0
redis,rocketmq的区分

将redis发布订阅模式用做消息队列和rabbitmq的区别 可靠性 redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中;...

osc_43of92sp
03/11
10
0
thinkphp-queue自带的队列包使用分析(转)

前言 当前笔记中的内容针对的是 thinkphp-queue 的 v1.1.2 版本,现在官方已经更新到了 v1.1.3 版本, 下文中提到的几个Bug在最新的master分支上均已修复。 笔记中的部分内容还未更新。 传统...

osc_31yjtdow
2019/03/18
8
0

没有更多内容

加载失败,请刷新页面

加载更多

垃圾收集器与内存分配策略

对象已死? 垃圾标记算法 1.引用计数算法 C++智能指针、Python 2.可达性分析算法 JavaGC Roots的根对象作为起始节点,通过引用链到某个对象不可达时,证明此对象不可能再被使用。 强引用:...

LoSingSang
昨天
27
0
Python--从集合中随机取出一个元素

Python--从集合中随机取出一个元素 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 有时候有一个这样的需求...

归子莫
昨天
27
0
iptables-F 后 SSH 连接断开

最近回收利用一台被征用做邮件服务的服务器,重新部署新的业务。 清理了所有的安装软件和目录文件后,调整了网络安全组规则,仅开放所需端口。 看了下防火墙的配置: # iptables -LChain I...

DEPAKIN
昨天
25
0
IDEA通过Maven打包JavaFX工程(OpenJFX11)

1 概述 最近研究JFX,写出来了但是打包不了,这。。。尴尬。。。 IDEA的文档说只支持Java8打成jar包: 尝试过直接使用Maven插件的package,不行,也尝试过Build Artifacts,也不行,各种奇奇...

氷泠
昨天
19
0
《一天一模式》— 命令模式

一、命令模式的概念 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。 二、什么时候使用命令模式 调用者与实现者通常是一种紧耦合的...

XuePeng77
昨天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部