使用redis的stream数据类型做消息队列
在redis5.0之前,如果想使用它作为简单的消息队列,最好的选择就是自身提供的pub/sub模式.它支持简单的发布/订阅模式,发布一个channel绑定一条消息,然后可以有多个消费者监听这个channel,每个消费者都能收到相同的消息。不支持持久化,不支持查询,不支持分组,不支持分片消费,也没有提供很好的监控手段(有简单的pubsub容器命令,可以看有哪些channel,订阅者数量等)。但是5.0之后,倘若我们人仍选择redis作为简单消息队列,就可以使用新的数据类型STREAM
STREAM数据类型介绍
数据类型基础说明
- 可以理解为一个有时间序列的一组数据集合,每一条新增的数据都是追加到数据集末尾,每一条数据都有自己的唯一id
- 底层数据结构是基数树
- 一个Stream可以有多个消费者分组group,每一个group也可以有多个消费者consumer,支持分片读取,全部读取,按照ID分段读取
- 随机访问时间复杂度是O(1),向流中添加一个条目的时间为O(1)。 访问任意一项的时间为O(n),其中n是ID的长度.
常用命令及详解
XADD 向指定的 Stream 添加一条新消息。
XADD key [MAXLEN [~] count] * field1 value1 [field2 value2 ...]
参数说明:
key:Stream 的名称。
MAXLEN [~] count:可选,限制 Stream 最大长度,超出自动裁剪最老消息。~ 表示近似修剪,性能更优。【实际上使用要注意,超过最大值直接丢弃,也就是“消失了“】
*:让 Redis 自动生成消息ID,也可自定义ID。
field value:消息体的键值对。
用法举例:XADD mystream * name Alice age 20
XRANGE 按ID范围读取 Stream 中的消息
XRANGE key start end [COUNT count]
参数说明:
start、end:起止ID,- 表示最小ID,+ 表示最大ID。
COUNT:可选,限制返回条数。
用法举例:XRANGE mystream - + # 读取所有消息
XREAD 从一个或多个 Stream 读取新消息,可阻塞等待
XREAD [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
参数说明:
BLOCK:可选,阻塞等待新消息的毫秒数。
STREAMS:后面跟 Stream 名称和起始ID。
用法举例:XREAD BLOCK 5000 STREAMS mystream $
$ 表示只读新消息XGROUP 创建、删除、管理 Stream 的消费者组。
XGROUP CREATE mystream mygroup 0-0 MKSTREAM
常用子命令:- 创建组:
XGROUP CREATE mystream mygroup 0-0 MKSTREAM
0-0:从头消费;$:只消费新消息。
MKSTREAM:Stream 不存在时自动创建 - 删除组:
XGROUP DESTROY mystream mygroup
- 创建消费者、删除消费者。一般不需要,会自动创建
XGROUP CREATECONSUMER mystream mygroup consumer-1
XGROUP CREATECONSUMER mystream mygroup consumer-1
- 创建组:
XREADGROUP 以消费者组身份读取消息,实现分布式并发消费
XREADGROUP GROUP group consumer [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
参数说明:
GROUP group consumer:指定组名和消费者名。
id:> 表示只读未分配的新消息,其他ID(如0)可用于补偿pending。
举例:XREADGROUP GROUP mygroup consumer-1 BLOCK 5000 STREAMS mystream >
XPENDING 查看某个组下所有未ack的消息(即已分配但未确认)注意这里不是消息的快照,它只是存储消息的ID列表,并不会复制一份消息内容
XPENDING key group [start end count [consumer]]
举例:XPENDING mystream mygroup - + 10
XPENDING mystream mygroup - + 10 consumer-1XACK 用于确认消息已被消费,也就是从pending状态PEL中移除
举例:XACK mystream mygroup 1680000000000-0XCLAIM/XAUTOCLAIM 将长时间未ack的pending消息转移到其他消费者/实现自动补偿。
举例:XCLAIM mystream mygroup consumer-2 60000 1680000000000-0
XAUTOCLAIM mystream mygroup consumer-2 60000 0-0 COUNT 10
XTRIM 限制流的最大长度,自动删除最老的消息。无论是否被ack的消息,都会被裁减。
语法:XTRIM key MAXLEN [~] count
举例:XTRIM mystream MAXLEN ~ 1000XDEL 从Stream中删除指定ID的消息,可以一次删除多个,用空格隔开即可
XDEL mystream 1680000000000-0
实际使用场景
可用作消息队列
- 当需要一个轻量级的、安全性要求比较低、可靠性不要求那么高的一个消息队列时,使用stream就很合适,性能也非常不错,单机能支持每秒几十万的写入
- 典型场景:订单异步处理、短信/邮件通知、日志收集、任务分发等
可以作为事件总线
- 作为事件总线,支撑微服务间的事件发布与订阅,作为事件源(例如,跟踪用户操作、点击等)。
- 例如:用户注册事件、支付完成事件等,多个服务可并发消费
延迟队列/死信队列
- 利用 Stream 的 pending/ack/xclaim 机制实现可靠的延迟消息、死信消息补偿。
实时数据流处理
- IoT、监控、风控等场景下,设备/传感器数据实时写入 Stream,后端实时消费分析。
- 支持高并发写入和多消费者并发处理
重要说明
关于持久化和消息删除
- 消息是默认就持久化的,并且并不提供设置过期时间,那么如果在消息量大且请求量大的情况下,会占用很多内存
- 如果在新增消息的时候使用maxlen选项限定了stream的长度,那么一定要考虑使用多个consumer,而且要提供一定的处理机制在某些consumer不可用的时候,将消息XCLAIM到可用的消费者。避免超过限定长度后,丢失消息。
- 不推荐每次消费完成后使用Xdel去删除,而是采用Xtrim收缩,结合Xinfo、Xlen等命令定期检测stream的长度,然后根据实际情况设置合理的收缩长度,定期的清理不再使用的消息。因为即使使用Xdel取删除消息,在当前的实现中,直到宏节点完全为空时才真正回收内存
读取的阻塞和非阻塞
- XRANGE 、XREAD 或 XREADGROUP ,没有BLOCK选项时,像任何其他Redis命令一样同步调用,此时他们就是同步命令;如果加上BLOCK选项就时非阻塞的,等待指定的毫秒直到有可以消费的消息并立即返回
插入的性能
- XADD 非常快,如果使用流水线,在普通机器中每秒可以轻松插入50万到100万项
- 以下是官网提供的延迟测试结果:【在这里,我们每次迭代最多处理10k条消息,这意味着 XREADGROUP 的 COUNT 参数被设置为10000。这增加了大量的延迟,但为了让缓慢的消费者能够跟上消息流,这是必需的。因此,你可以预期真实世界的延迟要小得多】
Results obtained: 结果:
Processed between 0 and 1 ms -> 74.11%
Processed between 1 and 2 ms -> 25.80%
Processed between 2 and 3 ms -> 0.06%
Processed between 3 and 4 ms -> 0.01%
Processed between 4 and 5 ms -> 0.02%
因此,99.9%的请求的延迟<= 2毫秒,异常值仍然非常接近平均值。
- 另外需要注意的是,从Redis 6.2.0版本开始,才增加了 IDLE 选项和独占范围间隔,虽然5.0就引入了stream数据类型
消费者组
- 何时不需要消费者组:如果你有一个数据流和多个客户端,而且你希望所有客户端都能收到所有信息,那么你就不需要消费者组。
- 如果你有一个数据流和多个客户端,而且你希望在客户端之间对数据流进行分区或分片,以便每个客户端都能获得到达数据流的消息的子集,那么你就需要一个消费者组。
- 当使用 XREADGROUP 读取时,服务器将记录哪些消息给到了哪些消费者:消息将存储在使用者组内的 Pending Entries List (PEL) 中,该列表是已传递但尚未确认的消息 ID 列表。
- 当实际场景是:可靠性不是必需的,并且偶尔的消息丢失是可以接受的情况下,可以使用 NOACK 子命令来避免将消息添加到 PEL。这相当于在读取消息时确认消息(自动ACK)。
- 使用 XREADGROUP 时,在 STREAMS 选项中指定的 ID 可以是以下两种之一:
- 特殊的 > ID,表示消费者只想接收从未发送给其他消费者的信息。它的意思是,给我新邮件。
- 任何其他 ID,即 0 或任何其他有效 ID 或不完整 ID(仅毫秒时间部分),都将导致返回发送命令的用户的待处理条目,且 ID 大于所提供的 ID。因此,基本上如果 ID 不大于,那么命令将只允许客户访问其待处理条目:已向其发送但尚未确认的信息。请注意,在这种情况下,BLOCK 和 NOACK 都会被忽略。
属于PEL中的消息可以删除吗
pending状态的消息是可以被删除的,redis并没有设计未确认的消息不允许删除。如果采用xdel删除消息后,pending列表将仍然保留待消费消息的ID,但是消息内容没有了。因此,在读取此类PEL条目时,Redis会返回一个空值。
一个stream的一个group多个consumer时如何消费的
1. 分区/竞争消费(Work Queue 模式)
- 每条消息只会被 group 下的一个消费者消费,不会被所有消费者都消费。
- Redis 会将新消息分配给 group 内“空闲”的消费者,实现消息的负载均衡(轮询或空闲优先,具体是由实现的客户端决定)。
- 多个消费者并发时,消息会被“分摊”到各个消费者,每个消息只会被其中一个消费。
- 消息被转XCLAIM到另一个消费者时会增加投递次数,并发时投递次数、时间戳都会变化,因此也只有一个消费者成功获取。XPENDING命令就可以看到每个消息被投递的次数
2. pending 机制
- 消费者用 XREADGROUP 拉取消息后,消息会进入该消费者的 pending(未确认)列表,直到被 XACK。
- 如果某个消费者挂掉,pending 里的消息可以被其他消费者用 XCLAIM/XAUTOCLAIM 方式“抢救”回来,保证消息最终被消费。
3. 分布式环境下的存储
- stream的增加数据和其他数据类型一样,都是需要一个唯一的key,然后给key绑定指定数据类型的一个或者多个值
- 那也就是说,即使在分布式存储环境下,它和其他的key一样,相同的key的数据一定存在同一个分片上(因为redis的分片机制就是按照Key来实现的)
- 实际使用时key的设置就要相对分散,否则数据会倾斜到某些节点上
x. 如果要“广播”效果(每个消费者都收到同一条消息),需要每个消费者用不同的 group。或者都广播了,就使用PUB/SUB吧,,~~
观测流
- Redis流和消费者组有不同的方式来观察正在发生的事情,比如前面说的XPENDING ,它允许我们检查在给定时刻正在处理的消息列表,以及它们的空闲时间和交付数量
- XINFO:这个命令使用子命令来显示流及其消费者组状态的不同信息。例如,XINFO流报告有关流本身的信息。可以用于Stream、Group、CONSUMERS
- 实际项目中可结合其他命令,直观的展示流的各种信息,比如有多少个分组、有哪些分组、有哪些消费者、消费者状态、消费进度、总条目数据等。有了这些信息就可以对消息的可靠性进行分析,还能及时发现资源占用情况,结合定时任务等作出具体性能调整。
更加详细stream的细节介绍,可以参考官网:https://redis.io/docs/latest/develop/data-types/streams
稍后我将具体介绍如何在代码中使用stream来作为消息队列。
使用redis的stream数据类型做消息队列的更多相关文章
- Redis中的Stream数据类型作为消息队列的尝试
Redis的List数据类型作为消息队列,已经比较合适了,但存在一些不足,比如只能独立消费,订阅发布又无法支持数据的持久化,相对前两者,Redis Stream作为消息队列的使用更为有优势. 相信 ...
- 使用Redis Stream来做消息队列和在Asp.Net Core中的实现
写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...
- 用redis实现支持优先级的消息队列
http://www.cnblogs.com/tianqiq/p/4309791.html http://www.cnblogs.com/it-cen/p/4312098.html http://ww ...
- 程序员过关斩将--redis做消息队列,香吗?
Redis消息队列 在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为 ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- 使用Redis做消息队列
基于内存的单线程数据库,使Redis的线程安全性极高.而Redis的双向链表数据类型(List)天生就可作为消息队列存储消息. 在这里就不说消息队列的等等一些优点.但是补充一下Redis的List类型 ...
- Redis 做消息队列
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...
- Redis做消息队列
1.连接从Redis中获取日志文件并存储到ES中 [root@Logstash ~]# vim /usr/local/logstash/config/redis.conf input { be ...
- 用 Redis 实现 PHP 的简单消息队列
参考:PHP高级编程之消息队列 消息队列就是在消息的传输过程中,可以保存消息的容器. 常见用途: 存储转发:异步处理耗时的任务 分布式事务:多个消费者消费同一个消息队列 应对高并发:通过消息队列保存任 ...
- Redis学习之实现优先级消息队列
很久没有写博客了,最近简单的学习了一下Redis,其中学习了一下用Redis实现优先级消息队列.关于更多更为详细的可以在www.redis.cn找到相关资料. 对于熟悉Redis的童鞋提到队列很自然的 ...
随机推荐
- Redis 高可用方案
本文分享自天翼云开发者社区<Redis 高可用方案>,作者:芋泥麻薯 一.常见使用方式 Redis的几种常见使用方式包括: Redis单副本: • Redis多副本(主从): • Redi ...
- 【Linux】5.1 Shell简介
Shell简介 1. Shell基础 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序,这 ...
- unigui的错误delphi clientHeight:property clientheight does not exist【10】
在unigui运行中发现这样的错误clientHeight:property clientheight does not exist. 这是啥原因.从老版本中复制过来的代码含dfm会出现这样的错误. ...
- unigui的程序编译后自动运行傻傻的手动【7】
我们每次修改unigui程序后,一般需要编译后执行,查看效果.可是每次都要关闭杀掉服务程序,再刷新浏览器才能实现. EMB应该知道这个反人类的做法吧.实际上提供了参数配置:自动kill服务程序,自动打 ...
- 基于transformer的机器翻译:手把手教你实现
目录 前言 transformer模型的搭建 Input embedding Encoder Decoder output transformer构建 data数据集处理 train config 参 ...
- 【杂谈】死锁?NO,时间跳跃!
在日常开发或线上运维中,我们经常会遇到各种数据库异常,例如超时.死锁等.但有些问题,表面看似平常,背后却藏着意想不到的原因. 今天就分享一次由服务器时间跳跃引发的 MySQL 获取锁超时问题的排查过程 ...
- 在 .NET 中的 ConvertAll 和 Select 方法哪个性能好
.NET 的 List 中提供了 ConvertAll 和 Select 两个方法,在开发中实际上应该使用哪一个? 接下来通过基准测试脚本来对比性能. 先编写基准测试脚本: [MemoryDiagno ...
- MIUI系统,APKMirror Installer安装apkm的时候提示app installation failed Installation aborted解决方案
场景 我的手机是MIUI系统,通过APKMirror Installer安装apkm的时候提示app installation failed Installation aborted. 本来不想装了, ...
- 如何用JavaScript纯前端来实现下载脚本
1.javascript脚本 function downloadFile(data, fileName, type="text/plain") { // 创建不可见的元素 cons ...
- 仿EXCEL插件,智表ZCELL产品V1.9 版本发布,增加导入、导出EXCEL功能
详细请移步 智表(ZCELL)官网www.zcell.net 更新说明 这次更新主要应用户要求,主要增加了导入.导出EXCEL文件功能,并增加了获取单元格公式.显示值等功能,欢迎大家体验使用. 本次 ...