Redis数据结构之Stream
Stream数据结构简介
Stream是Redis5推出的新的一种数据结构。他是一个新的强大的支持多播的可持久化的消息队列。
Redis官方作者声明借鉴了kafka的设计。
往队列中添加消息 (xadd)
xadd steam_user * username zhangsan age 18

xadd 命令参数解释
steam_user 就是一个key,因为stream本身也是redis的一种存储数据的一种数据结构。所以就是key,这里也是这个队列的名称。* 就是让redis给我们生成这条消息的id,也就是控制台返回的1743306461438-0。这个id由两部分组成。一部分是时间戳(毫秒单位),另一部分是序号。这个时间就是生成消息的时间。那为什么还要加个序号呢?因为为了保证消息是有序的,因此redis生成的id单调递增有序的。由于id中包含了时间戳部分,为了避免服务器时间错误而带来的问题(例如服务器时间延后了),redis的每个Stream类型都会维护一个latest_generated_id属性,用于记录最后一个消息的id。若发现当前时间戳退后(小于latest_generated_id所记录)的,则采用时间戳不变而序号递增的方式来作为新消息id。
消息id是消息非常重要的一个标识,如果没有特殊需求,就用redis自带的生成策略。
*后面的内容就是消息体。
获取消息列表,会自动过滤已经删除的消息 (xrange)
xrange steam_user - +

-符号表示消息id的最小值,+表示消息id的最大值。那么这个命令也就是取所有的消息。当然也可以指定消息id的起始值和终值。

由于我的消息id都比1000要大,所以都出来了。这个命令不难理解,大家自己玩一玩吧。
xdel (删除消息)
xdel steam_user 1743306461438-0。删除steam_user 队列中,id为1743306461438-0的消息。执行一下xrange查看一下结果。

果然消息已经被删除了。我们来执行xlen命令,这个命令是来获取队列中的消息个数的,来看看消息的条数是否变了。

简单的往队列中添加消息的命令了解完毕后,那么接下来就是来消费队列中的消息了。由于借鉴了kafka的设计,Stream引入了消费者组的功能。但是Stream也可以不定义消费者组来进行消息的消费。当Stream没有新消息时,甚至可以阻塞等待。Stream有个xread命令,可以将Stream当成list数据结构来使用。
xread 命令详解
xread count 1 streams steam_user 0-0
count表示要消费几条消息。streams 关键字,后面是对应的消息队列名称,也就是redis中的key。0-0表示从头开始消费(也就是谁在队列的最前面)。

也可以指定从消息id开始后进行消费,但是不包括命令中的消息id。先来查看一下我们的队列中的所有消息。

现在有四条消息,我准备消费3、4两条消息,也就是username是ww和bb的,那么对应的指令就是
xread count 2 streams steam_user 1743309115543-0

成功。
按道理来说,既然消息被消费了,消息应该不在队列中了。但是我们用xrange命令查看一下,发现还在队列中。

这跟我们常用的rabbitmq是不一样的,使用的时候要小心。
xread count 1 streams steam_user $
上面这个命令表示获取在当前时间加入到队列中尾部的消息,由于很难演示出来,执行这个命令返回的是null

所以我们可以考虑让这条命令阻塞式执行,只要有新的消息加入到队列,那么这个命令就会执行并且返回结果。
那么新的命令就是加了一些参数:
xread block 0 count 1 streams steam_user $
block关键字,0表示阻塞时间,单位为毫秒,0表示一直阻塞。当执行这个命令后,控制台就阻塞了。

那我们在另一个控制台来新增一条消息。

这时候,阻塞的客户端也成功消费到消息了:

他还贴心的告诉你,阻塞了159秒。
在用xread进行消费的时候,我们要记录消费到哪里了,方式有很多,比如记录消费的消息id。下次继续xread的时候,将上次返回的最后一个消息id作为参数传递过去继续进行下一轮的消息消费。
Stream消费者组
创建消费者组
Stream 通过 xgroup create 指令创建消费组 (Consumer Group),需要传递起始消息 ID 参数用来初始化 last_delivered_id 变量。
指令:xgroup create steam_user st_group 0-0
参数解释: xgroup create 关键字,固定格式。steam_user 表示在哪个队列创建消费者组,st_group 表示消费者组的名称。0-0表示从队列头部开始消费(也就是最先入队的消息),也可以指定从哪个消息id开始,xgroup create steam_user st_group 1743309450469-0,但是不包含这个id。
还有个相反的命令:xgroup create steam_user st_g2 $。$表示从尾部开始消费,只接收新消息,当前stream消息会全部忽略。
消费者组创建好了,那么我们现在就可以消费了。
消费命令:
xreadgroup group st_group consumer1 count 1 streams steam_user >

xreadgroup group 关键字,写死。st_group 创建的组名。consumer1 消费者名称。随便起。count 要消费的消息数量
streams 关键字。steam_user 队列名称。> 表示当前消费组的 last_delivered_id 后面开始读,每当消费者读取一条消息,last_delivered_id 变量就会前进。
由于当前消费者组是从头开始读取,所以第一条就是lisi这个数据,没错。

我们继续执行消费者组消费的命令,一直执行到没有消息输出为止。

这时候,消费者组已经消费完全部消息。但是,这个队列中的消息仍然存在啊,这别忘记了。只是对于当前消费者组而言,他们已经消费完毕了,哪怕你在这个组中换一个消费者也不能消费消息了。

消费者名字我换成了consumer2,但是已经没有消息消费了。但是如果我们换一个消费者组的话,仍然是能消费到消息的。
重新创建一个消费者组,就当复习相关命令了。
xgroup create steam_user st_gp2 0-0
然后再次执行组内消费命令:
xreadgroup group st_gp2 consumer1 count 1 streams steam_user >

继续执行该命令,一直消费完毕为止。

好的,到此为止,演示完毕消费者组的消费消息。
既然没有消息进行消费者组进行消费了,所以我们可以利用阻塞等待机制进行阻塞消费,命令:
xreadgroup group st_gp2 consumer1 block 0 count 1 streams steam_user >
似曾相识,就是加了block关键字,0表示一直阻塞到有新的消息,单位是毫秒。
好,我们在另一个客户端往steam_user 队列中放入消息。

此时阻塞那边的客户端也消费到了这条消息。

完美。
我们还可以通过命令来查看一个队列中消费组的相关信息,命令:
xinfo groups steam_user
xinfo groups 固定写法,steam_user是队列名称。也就是key

返回的名次解释
name:组名称
consumers:消费者数量
pending:未确认消费的数量(ack)
last-delivered-id: 消费到的最后一条消息的消息id
我们还可以查看某个组内的消费者的详细信息,命令:
xinfo consumers steam_user st_group
xinfo consumers 固定格式,steam_user,队列名称,st_group,组名

返回值解释:
name:消费者名称
pending:未确认的消息数量
idle:空闲了多少时间没有消费消息。单位毫秒
如果我们确认一条消息,那么pengding就变成了5。确认命令:
xack steam_user st_group 1743310561766-0
xack 关键字,steam_user 队列名称。st_group 组名 1743310561766-0 消息id
1743310561766-0这个消息id正是st_group 消费的最后一条消息。好,继续查看st_group组的消费者信息:
xinfo consumers steam_user st_group

果然确认了。完美。
xack能有批量确认的功能。那么首先要查询到某个消费者组总共确认消费了多少消息,也就是消费的消息id,怎么查询呢?来看命令:
XPENDING <stream> <group> [<start> <end> <count> [<consumer>]]
stream 队列名称
group 组名称
start 从哪个id开始 可以使用 - 符号
end 从哪个id结束 可以使用 + 符号
count 返回多少条消息
consumers 可选值,表示指定哪个消费者

返回的是5个,那就对上了,因为我们已经手动ack了其中一条消息。好的,查询到了以后,我们来进行批量ack操作

再次查看消费者组的信息,xinfo consumers steam_user st_group

已经没有了未确认的消息
说实话,这种确认方式挺low的,要是有这个命令该有多好啊
xack steam_user st_group - +,可惜没有。。。

也难怪,redis的Stream消息队列很少有人使用。但是如果你的项目不想引入那么多中间件,用redis的stream来做消息队列是一个不错的选择。
那么有没有一条命令,就是我消费了并且进行ack。不好意思,没有,如果想实现,可以使用lua脚本进行实现。
stream队列有哪些问题?
首先第一个大问题,我认为是bigkey问题。由于stream队列在消费者组进行消费完后,不会对消息进行删除操作。所以随着消息的频繁创建,这个stream总有一天会成为一个bigkey。而bigkey会影响到redis的整体性能。
针对这个问题的解决方案也不难,那就是当所有的消费者组在消费完某条消息后,即时的将这条消息删除掉。
第二就是没有一个web页面来管理监控stream消息队列,如果要做还要自己单独去实现,但是像rabbitmq和rocketmq都有自己的web控制台页面。
本文来自博客园,作者:诸葛匹夫,转载请注明原文链接:https://www.cnblogs.com/shenxingzhuge/p/18800415
卡里离冰冷的40个亿还差39多个亿
浙公网安备 33010602011771号