Redis Stream 特性是Redis 5.0之后才有的。Redis Stream的主要应用就是时间序列的消息流分发。PUB/SUB也可以做消息流分发,但是PUB/SUB不记录历史消息,而Redis Stream可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
Redis stream也是有长度大小限制的,超过设置的最大长度,最旧的消息会被丢失
用法
向队列添加消息,同时也创建一个队列
> XADD mystream * sensor-id 1234 temperature 19.8
1518951480106-0
- mystream是队列的名字,如果没有会创建
- * 是消息id,使用*表示由redis生成(推荐),可以自定义,但是要自己保证递增性。
- sensor-id 1234 temperature 19.8 是一条记录
- 1518951480106-0,返回的消息ID,是毫秒+一个从0开始的数字
使用XADD创建一个stream的时候,可以设置一个最大长度MAXLEN,由于Redis Sream的内部实现,精确的设置一个长度上限消耗比较大,所以XADD提供了一个模糊设置方式:
> XADD mystream MAXLEN ~ 1000 * sensor-id 1234 temperature 19.8
意思是长度可以超过1000一些,由Redis自己觉得什么时候截断,但是截断以后一定不要小于1000。
下边是对于Stream的一些基本操作:
获取消息队列长度:
> XLEN mystream
(integer) 2
通过ID范围获取消息:
> XRANGE mystream - +
1) 1) 1518951480106-0
2) 1) "sensor-id"
2) "1234"
3) "temperature"
4) "19.8"
- - + 表示获取所有, -表示最小ID, +表示最大ID
- 如果使用的是redis默认的ID,就可以通过时间范围来查询消息了,这就是为什么推荐使用默* 认ID的原因
- 这个命令之后还可以跟一个count参数,如果ID范围间隔过大,可以使用count做分页
- XREVRANGE命令是对应的逆序查找
通过XREAD获取消息:
> XREAD COUNT 2 STREAMS mystream 0
1) 1) "mystream"
2) 1) 1) 1519073278252-0
2) 1) "foo"
2) "value_1"
2) 1) 1519073279157-0
2) 1) "foo"
2) "value_2"
- count 参数表示需要获取多少个,还是分页用,如果用0,表示任意多
- STREAMS后边可以跟多个队列,也就是说一个客户端可以同时监听多个消息队列
- 最后的0表示从那个ID开始读,0表示从头开始读,如果分页,就指定上次读取的最后一个消息ID
- 这种读取方式比较开放,所有客户端都可以读任意时刻的消息。
阻塞的读消息:
> XREAD BLOCK 0 STREAMS mystream $
- BLOCK后边的数字表示timeout,0的意思是不超时
- $表示等待新消息,如果指定当前时间之前的ID,则返回已有消息而不会阻塞。所以在使用*BLOCK的时候,这个值总是$
- 如果有多个客户端等待同一个队列,当队列添加一个新消息的时候,所有客户端都会收到这个消息,策略为FIFO。
上述的基本操作都比较自由,可以根据实际需要自己灵活组合使用,但是Redis Stream最重要的也是最常用的一种使用模式是Consumer Group。
使用Consumer groups消费时间序列
在使用consumer group之前应该先了解一些概念:
- stream是一个序列,使用XADD向其中添加的消息不会被自动删除(在stream长度限制之内),所有消息始终在那里。
- consumer group是服务端的一个结构,它维护了属于这个group的consumer的状态,即这个consumer处理了那些消息,正在处理哪些消息。并保证同一个消息不会分发给多个consumer。
- consumer通过唯一标识认定,即使换了客户端,只要ID相同,group仍然认为是同一个consumer。
创建一个Group:
> XGROUP CREATE mystream mygroup $
OK
- stream必须已经存在
- $意思仍旧是从新消息开始分发,注意因为group是在服务端的结构,所以这里用的词是分发,而不是接收。
向队列添加新消息:
> XADD mystream * message apple
1526569495631-0
> XADD mystream * message orange
1526569498055-0
> XADD mystream * message strawberry
1526569506935-0
> XADD mystream * message apricot
1526569535168-0
> XADD mystream * message banana
1526569544280-0
客户端读取消息:
> XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) 1526569495631-0
2) 1) "message"
2) "apple"
- 这个客户端将自己标识为Alice
- > 的意思是,只把没有分发给别人的消息发给我,这也是最常用的方式。
- 如果使用其他符号,意思是获取自己的pending状态的消息,也就是没有ACK的消息,如果没有pending状态的消息,返回空列表。这个功能很重要,一个consumer启动后,应该先读自己的pending消息(因为之前可能Crash过),如果没有pending消息了才开始处理新消息。
- 可以同时读多个stream,但是这些stream都需要有相同名字的group
XREADGROUP是一个写操作,只能在master节点上执行
读自己的pending消息:
> XREADGROUP GROUP mygroup Alice STREAMS mystream 0
确认消息:
> XACK mystream mygroup 1526569495631-0
(integer) 1
- XACK表示这个消息处理完了
- XACK这个命令并不需要指定消费者的名字,也就是说可以强行结束他人正处理的任务,但是不要这样做。
- 这个命令可以重复执行,没有副作用。第二次以后执行返回0
错误处理
假如一个consumer有pending的消息,然后crash了,然后再也不启动了,怎么办呢?Redis Stream提供了查询消息处理状态的命令XPENDING和转移消息所有者的命令XCLAIM
使用一个或者多个客户端,获取消息处于pending状态,指定group返回一个Summary:
> XPENDING mystream mygroup
1) (integer) 5
2) "1564401080073-0"
3) "1564401244891-0"
4) 1) 1) "Alice"
2) "1"
2) 1) "Bob"
2) "2"
3) 1) "Lily"
2) "2"
- 5表示个数
- 然后是最小ID和最大ID(中间的消息可以不都是pending状态的)
- 接下来显示每个consumer都有几个pending的消息
然后使用XPENDING命令获取每个Pending消息的信息
> XPENDING mystream mygroup 1564401080073-0 1564401244891-0 10
1) 1) "1564401080073-0"
2) "Alice"
3) (integer) 1818502
4) (integer) 4
2) 1) "1564401116987-0"
2) "Bob"
3) (integer) 590328
4) (integer) 1
3) 1) "1564401230270-0"
2) "Bob"
3) (integer) 590328
4) (integer) 1
4) 1) "1564401237686-0"
2) "Lily"
3) (integer) 416216
4) (integer) 1
5) 1) "1564401244891-0"
2) "Lily"
3) (integer) 416216
4) (integer) 1
使用范围和个数查询可以看到pending状态的消息在谁那里,停留了多少毫秒。对于停留时间过长的消息,可以使用XCLAIM将消息交给其他consumer处理。
> XCLAIM mystream mygroup Alice 50000 1564401116987-0
- 这句的意思是将1564401116987-0这个任务交由Alice处理
- 50000表示的是最小闲置时间,为什么要设置这个呢,这个任务闲置了多长时间不是在XPEDING命令中得到了么?因为可能有多个客户端发现了这个闲置的任务,都要这个任务的处理权,而在XCLAIM命令中,并没有说从谁那里拿来任务,所以可能造成一个客户端刚刚拿到任务,就被另外一个客户端拿走了。指定了一个最小时间,紧接着的第二个CLAIM命令就无法拿到任务了。
如果一个任务反复失败怎么办?
使用XPENDING命令可以查看一个任务被调度的次数,使用XREADGROUP和CLAIM都会增加一个任务被调度的次数。当这个次数达到一个设定的上限的时候,最好的处理方式是将这个任务放到另外一个Stream中,并通知任务失败。
这种任务也叫死信
Redis Stream中的消息和Group状态都会从主节点复制到从节点,并保持到AOF和RDB文件中。因此重启Redis会保存Stream中的状态。但是主节点崩溃仍可能有一些数据没有同步过去,如果应用程序对数据要求较高,可以使用WAIT命令等待数据主从同步完成。即使这样,在主库Crash的时候,还是有可能会丢失数据。
使用XINFO观察Stream
XINFO命令有很多子命令,比如查看Stream本身的状态使用XINFO STREAM
> XINFO STREAM mystream
1) length
2) (integer) 13
3) radix-tree-keys
4) (integer) 1
5) radix-tree-nodes
6) (integer) 2
7) groups
8) (integer) 2
9) first-entry
10) 1) 1524494395530-0
2) 1) "a"
2) "1"
3) "b"
4) "2"
11) last-entry
12) 1) 1526569544280-0
2) 1) "message"
2) "banana"
使用XINFO GROUPS查看组信息
> XINFO GROUPS mystream
1) 1) name
2) "mygroup"
3) consumers
4) (integer) 2
5) pending
6) (integer) 2
2) 1) name
2) "some-other-group"
3) consumers
4) (integer) 1
5) pending
6) (integer) 0
查看某一个Group中的Consumer:
> XINFO CONSUMERS mystream mygroup
1) 1) name
2) "Alice"
3) pending
4) (integer) 1
5) idle
6) (integer) 9104628
2) 1) name
2) "Bob"
3) pending
4) (integer) 1
5) idle
6) (integer) 83841983