Redis 队列与 Stream
Redis5.0 最大的新特性就是多出了一个数据结构 Stream,它是一个新的强大的 支持多播的可持久化的消息队列,作者声明 Redis Stream 地借鉴了 Kafka 的设计。
Stream 总述
Redis Stream 的结构如上图所示,每一个Stream都有一个消息链表,将所有加入 的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容。消息是持久化的, Redis 重启后,内容还在。
每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用xadd指 令追加消息时自动创建。
每个 Stream 都可以挂多个消费组,每个消费组会有个游标last_delivered_id在 Stream 数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费 组都有一个 Stream 内唯一的名称,消费组不会自动创建,它需要单独的指令 xgroup create进行创建,需要指定从 Stream 的某个消息 ID 开始消费,这个 ID 用来初始化last_delivered_id变量。
每个消费组 (Consumer Group) 的状态都是独立的,相互不受影响。也就是 说同一份 Stream 内部的消息会被每个消费组都消费到。 同一个消费组 (Consumer Group) 可以挂接多个消费者 (Consumer),这些消费 者之间是竞争关系,任意一个消费者读取了消息都会使游标last_delivered_id往 前移动。
每个消费者有一个组内唯一名称。 消费者 (Consumer) 内部会有个状态变量 pending_ids,它记录了当前已经被 客户端读取,但是还没有 ack 的消息。如果客户端没有 ack,这个变量里面的消 息 ID 会越来越多,一旦某个消息被 ack,它就开始减少。这个 pending_ids 变 量在 Redis 官方被称之为 PEL,也就是 Pending Entries List,这是一个很核心的 数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢 失了没处理。
消息 ID 的形式是 timestampInMillis-sequence,例如 1527846880572-5,它 表示当前的消息在毫米时间戳 1527846880572 时产生,并且是该毫秒内产生的第 5 条消息。消息 ID 可以由服务器自动生成,也可以由客户端自己指定,但是形 式必须是整数-整数,而且必须是后面加入的消息的 ID 要大于前面的消息 ID。 消息内容就是键值对,形如 hash 结构的键值对,这没什么特别之处。
常用操作命令
生产端
xadd 追加消息
xdel 删除消息,这里的删除仅仅是设置了标志位,不会实际删除消息。
xrange 获取消息列表,会自动过滤已经删除的消息
xlen 消息长度
del 删除 Stream
xadd streamtest * name mark age 18
streamtest 表示当前这个队列的名字,也就是我们一般意义上 Redis 中的 key, * 号表示服务器自动生成 ID,后面顺序跟着“name mark age 18”,是我们存入 当前 streamtest 这个队列的消息,采用的也是 key/value 的存储形式
返回值 1626705954593-0 则是生成的消息 ID,由两部分组成:时间戳-序号。 时间戳时毫秒级单位,是生成消息的 Redis 服务器时间,它是个 64 位整型。序 号是在这个毫秒时间点内的消息序号。它也是个 64 位整型。
为了保证消息是有序的,因此 Redis 生成的 ID 是单调递增有序的。由于 ID 中包含时间戳部分,为了避免服务器时间错误而带来的问题(例如服务器时间延 后了),Redis 的每个 Stream 类型数据都维护一个 latest_generated_id 属性,用 于记录最后一个消息的 ID。若发现当前时间戳退后(小于 latest_generated_id 所 记录的),则采用时间戳不变而序号递增的方案来作为新消息 ID(这也是序号 为什么使用 int64 的原因,保证有足够多的的序号),从而保证 ID 的单调递增性 质。
如果不是非常特别的需求,强烈建议使用 Redis 的方案生成消息 ID,因为这 种时间戳+序号的单调递增的 ID 方案,几乎可以满足全部的需求,但 ID 是支持 自定义的。
xrange streamtest - +
其中-表示最小值 , + 表示最大值
或者我们可以指定消息 ID 的列表:
xdel streamtest 1626706380924-0
xlen streamtest
del streamtest 删除整个 Stream
消费端
单消费者
虽然 Stream 中有消费者组的概念,但是可以在不定义消费组的情况下进行 Stream 消息的独立消费,当 Stream 没有新消息时,甚至可以阻塞等待。Redis 设 计了一个单独的消费指令 xread,可以将 Stream 当成普通的消息队列 (list) 来 使用。使用 xread 时,我们可以完全忽略消费组 (Consumer Group) 的存在,就 好比 Stream 就是一个普通的列表 (list)。
xread count 1 streams stream2 0-0
“count 1”表示从 Stream 读取 1 条消息,缺省当然是头部,“streams” 可以理解为 Redis 关键字,“stream2”指明了要读取的队列名称,“0-0”指从 头开始
xread count 2 streams stream2 1626710882927-0
也可以指定从 streams 的消息 Id 开始(不包括命令中的消息 id)
xread count 1 streams stream2 $
$代表从尾部读取,上面的意思就是从尾部读取最新的一条消息,此时默认不 返回任何消息
所以最好以阻塞的方式读取尾部最新的一条消息,直到新的消息的到来
xread block 0 count 1 streams stream2 $ block 后面的数字代表阻塞时间,单位毫秒此时我们新开一个客户端,往 stream2 中写入一条消息
可以看到阻塞解除了,返回了新的消息内容,而且还显示了一个等待时间, 这里我们等待了 127.87s
一般来说客户端如果想要使用 xread 进行顺序消费,一定要记住当前消费 到哪里了,也就是返回的消息 ID。下次继续调用 xread 时,将上次返回的最后 一个消息 ID 作为参数传递进去,就可以继续消费后续的消息。