流(Stream)是Redis 5.0版本中新增的数据结构,也是该版本最重要的更新。在以往的版本中,为了实现消息队列这一常见的应用,用户往往会使用列表,有序集合和发布与订阅这3个功能,但这些不同的实现都有各自的缺陷:
除了以上3种数据结构各自有的问题之外,还有一个问题是3种数据结构共有的:无论是列表、有序集合,还是发布与订阅,它们的元素都只能是单个值。
Redis流的出现解决了上述提到的所有问题,它是上述3种数据结构的综合体,具备它们各自的所有优点以及特点,是使用Redis实现消息队列应用的最佳选择。
流是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值对,这些元素会根据ID的大小在流中有序地进行排列。
下表列出了Redis为流提供的操作命令及其参数与说明:
命令 | 用法及参数 | 说明 |
---|---|---|
XADD | XADD stream id field value [field value ...] |
将一个带有指定ID以及包含指定键值对的元素追加到流的末尾 |
XTRIM | XTRIM stream MAXLEN len |
对流进行修剪 |
XDEL | XDEL stream id [id ...] |
移除一个或多个指定ID的流元素 |
XLEN | XLEN stream |
获取流中包含的元素数量 |
XRANGE | XRANGE stream start-id end-id [COUNT n] |
以遍历或迭代的方式访问流中的一个或多个元素 |
XREVRANGE | XREVRANGE stream end-id start-id [COUNT n] |
以逆序的方式访问流元素 |
XREAD | XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ... |
以阻塞或非阻塞方式访问流 |
XGROUP CREATE | XGROUP CREATE stream group start_id |
创建消费者组 |
XREADGROUP | XREADGROUD GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...] |
读取消费者组的消息 |
XACK | XACK stream group id [id id ...] |
告知Redis消息处理已完成 |
XGROUP SETID | XGROUP SETID stream group id |
修改指定消费者组的最后递送的消息ID |
XGROUP DELCONSUMER | XGROUP DELCONSUMER stream group consumer |
删除指定的消费者 |
XGROUP DESTROY | XGROUP DESTROY stream group |
删除消费者组 |
XPENDING | XPENDING stream group [start stop count] consumer |
获取指定流的指定消费者组的待处理消息信息 |
XCLAIM | XCLAIM key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] |
转移消息的归属权 |
XINFO | XINFO [CONSUMERS key groupname] [GROUPS key] [STREAM key] [HELP] |
查看流和消费者组的相关信息 |
用户可以通过使用XADD命令,将一个带有指定ID以及包含指定键值对的元素追加到流的末尾:
XADD stream id field value [field value ...]
如果给定的流不存在,那么Redis会先创建一个空白的流,然后将给定的元素追加到流中。
流中的每个元素可以包含一个或任意多个键值对,并且同一个流中不同元素可以包含不同数量的键值对。
注意: 流会以有序方式存储用户给定的键值对:用户在创建元素时以什么顺序给定键值对,它们在被取出的时候就是什么顺序。 |
流元素的ID由毫秒和顺序编号两部分组成,其中使用UNIX时间戳表示的毫秒时间用于标识与元素相关联的时间,而以0为起始值的顺序编号则用于区分同一时间内产生的多个不同元素。因为毫秒时间和顺序编号都是使用64位的非负整数表示,所以整个流ID的总长为128位,而Redis在接受流ID输入及展示的时候都会使用连接符 “-” 分割这两部分。
1100000000000 - 12345 |
127.0.0.1:6379> XADD s1 1100000000000-12345 k1 v1
"1100000000000-12345"
用户在输入流ID的时候,除了可以给出带有毫秒时间和顺序编号的完整流ID之外,还可以给出只包含毫秒时间的不完整的流ID:在这种情况下,Redis会自动将ID的顺序编号部分设置为0 :
127.0.0.1:6379> XADD temp-stream 1000000000000 k1 v1
"1000000000000-0"
因为同一个流中的每个元素的ID都用于指定特定的元素,所以这些ID必须是各不相同的,即在同一个流中不同元素是不允许使用相同的ID的,尝试使用相同ID的时候会引发错误。
127.0.0.1:6379> XADD s1 1100000000000-12345 k1 v1
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
除了不允许使用相同的ID之外,Redis还要求新元素的ID必须比流中所有已有元素的ID都要大
流元素ID比较规则:
由于Redis流对ID的限制,使得Redis从逻辑上将流变成了一种只执行追加操作(append only)的数据结构,这种特性对于使用流实现消息队列和事件系统来说非常重要,因为这可以让用户确信,新的消息和事件只会在已有消息和事件之后。此外,只能将新元素添加到末尾而不允许在数据结构的中间添加新元素,这也是流与列表及有序集合之间的一个显著区别。
Redis对元素ID的要求非常严格,并且还会拒绝不符合规则的ID。为了用户方便执行操作,Redis为XADD命令的ID参数设定了一个特殊值:"*" 当用户使用符号 *作为ID时,Redis将自动为新元素生成一个可用的新ID:
127.0.0.1:6379> XADD s1 * k1 v1
如果用户使用了 * 作为ID参数的值,但是宿主机器的当前时间比流中已有最大ID的毫秒时间要小,那么Redis将使用该ID的毫秒时间作为新ID的毫秒时间,以此来避免机器时间倒流产生错误。 |
除了上面提到的各种参数以外,XADD命令还提供了MAXLEN 选项,让用户可以在添加新元素的同时删除旧元素,以此来限制流的长度:
XADD stream [MAXLEN len] id field value [field value ...]
在将新元素追加到流的末尾后,XADD命令就会按照MAXLEN选项指定的长度,按照先进先出的规则移除超出长度限制的元素。
复杂度: O ( l o g ( N ) ) O(log(N)) O(log(N)),其中 N N N为流目前包含的元素数量。
版本要求:XADD命令从Redis 5.0版本开始可用。
用户除了可以在执行XADD命令的同时使用MAXLEN选项对流进行修剪之外,还可以通过执行XTRIM命令直接将流修剪到指定长度:
XTRIM stream MAXLEN len
XTRIM命令在执行之后会返回被移除的元素的数量。
复杂度: O ( l o g ( N ) + M ) O(log(N)+M) O(log(N)+M),其中 N N N为执行修剪操作前流包含的元素数量,而 M M M为被移除元素的数量。
版本要求:XTRIM命令从Redis 5.0版本开始可用。
XDEL命令接受一个流以及任意多个元素的ID作为输入,并从流中移除ID对应的元素:
XDEL stream id [id ...]
XDEL命令在执行成功之后返回被移除元素的数量。
复杂度: O ( l o g ( N ) ∗ M ) O(log(N)*M) O(log(N)∗M),其中 N N N为流包含的元素数量,而 M M M为被移除元素的数量。
版本要求:Redis 5.0版本开始可用。
使用XLEN命令可以获取流中包含的元素数量:
XLEN stream
复杂度: O ( 1 ) O(1) O(1)
版本要求:Redis 5.0版本开始可用。
正如开头所说,流本身是一个有序序列,对于这种序列,使用有序方式获取序列的各个元素是一种非常常见的操作。类似于字符串类型键的LRANGE命令一样,XRANGE命令也可以以遍历或迭代的方式,访问流中的单个或任意多个远:
XRANGE stream start-id end-id [COUNT n]
根据给定的参数不同,XRANGE命令可以实现不同的功能:
可以使用XRANGE命令对流进行迭代,步骤如下:
使用XREVRANGE命令可以按照ID从大到小的顺序访问流中的元素:
XREVRANGE stream end-id start-id [COUNT n]
复杂度: O ( l o g ( N ) + M ) O(log(N)+M) O(log(N)+M),其中 N N N为流包含的元素数量,而 M M M为命令返回的流元素数量。
版本要求:Redis 5.0版本开始可用。
与XRANGE命令和XREVRANGE命令不同,XREAD命令只能从一个方向对流进行迭代,但是它提供了更简单的迭代API,支持同时对多个流进行迭代,并且能够以阻塞和非阻塞两种方式执行:
XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...
XREAD命令最基础的用法就是从多个流中取出大于指定ID的多个元素,其中紧跟在STREAMS选项之后的就是流的名字以及与之对应的元素ID:
XREAD STREAMS stream1 stream2 stream3 ... id1 id2 di3 ...
在调用XREAD命令时,用户需要先给定所有想要从流中获取元素的流,然后再给出与各个流相对应的ID。除此之外,还可以使用可选项 COUNT 限制命令对于每个流最多可以返回多少个元素:
XREAD [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...
注意: 可选项 COUNT 必须放在 STREAMS 选项之前,这是因为 STREAMS 选项是一个可变参数选项,所以必须放在最后一个选项。 |
使用XREAD命令迭代流步骤如下:
通过使用可选项 BLOCK 并给定一个毫秒级精度的超时时间作为参数,能够以可阻塞的方式执行 XREAD命令:
XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 di3 ...
在以阻塞方式获取流元素的时候,尝尝会出现要获取最新出现的元素的场景,为了解决这个问题,Redis为XREAD命令提供了一个特殊ID参数 $ 符号,用户在执行阻塞式的XREAD命令时,只要将 $ 符号作为ID参数的值,命令就会只获取给定流在执行命令之后新出现的元素:
XREAD BLOCK ms STREAMS stream1 stream2 stream3 ... $ $ $ ...
复杂度:对于用户给定的每个流,获取流元素的复杂度为 O ( l o g ( N ) + M ) O(log(N)+M) O(log(N)+M),其中 N N N为流包含的元素数量, M M M为被获取的元素数量。对于给定的 I I I个流,获取元素的总复杂度为 O ( ( l o g ( N ) + M ) ∗ I ) O((log(N)+M)*I) O((log(N)+M)∗I) 。
版本要求:Redis 5.0.0版本开始可用。
Redis流的消费者组允许用户将一个流从逻辑上划分为多个不同的流,并让消费者组属下的消费者去处理组中的消息。
创建消费者组的操作可以通过执行 XGROUP CREATE命令来完成,该命令是 XGROUP命令的一个子命令:
XGROUP CREATE stream group start_id
其中 stream参数表示流的名字,group参数表示要创建的消费者组的名字,start_id参数用于指定消费者组在流中的起始ID,这个ID决定了消费者组要从流的哪个ID之后开始读取。例如:如果用户将 0 作为start_id参数的值,那么说明希望从流的开头进行读取;如果以 $ 作为 start_id参数的值,则表示从最新的元素开始读取。
同一个流的消息在不同消费者组之间是共享而不是独占的,也就是说,流中的同一条消息可以被多个不同组的消费者读取。并且来自不同消费者组的读取操作不会对其他消费者组的读取产生任何影响。
客户端可以通过执行 XREADGROUP命令来读取消费者组中的消息:
XREADGROUD GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...]
这个命令的基本参数与 XREAD命令大同小异,主要区别在于新增的GROUP group consumer选项,该选项的两个参数分别用于指定被读取消费者组以及负责处理消息的消费者。
消费者组在创建之后,就会跟踪并维护一系列的信息与数据结构,其中包括:
从逻辑上说,消费者就是负责处理消息的客户端。与创建消费者组不一样,消费者不用显式的创建,用户只要在执行 XREADGROUP命令时给定的消费者的名字,Redis就会自动为新出现的消费者创建相应的数据结构。
与消费者组一样,消费者也会维护一个属于自己的待处理消息队列:每当用户使用XREADGROUP命令读取一条消息,并将这条消息指派给一个消费者处理时,该消费者就会把所指派的消息添加到自己的待处理消息队列中。
需要注意的是,与多个消费者组能够共享同一个流中的元素不一样,同一消费者组中的每条消息只能有一个消费者,不同消费者将独占组中的不同消息:当一个消费者读取一条消息后,同组的其他消费者将无法读取这条消息。
当消费者处理完一条消息之后,它需要向Redis发送一条针对该消息的XACK命令:
XACK stream group id [id id ...]
当Redis接收到消费者发送来的XACK命令之后,就会从消费者组的待处理消息队列以及消费者的待处理消息队列中移除指定的消息。这样一来,这些消息的状态就会从 待处理 转换为 已处理,具体过程如下所示:
通过执行 XGROUP CREATE命令,用户可以为流创建一个具有指定名称的消费者组:
XGROUP CREATE stream group id
如果给定的流不存在,那么将返回一个错误。
复杂度: O ( 1 ) O(1) O(1)
版本要求:Redis 5.0版本开始可用。
对于一个已经存在的消费者组来说,用户可以通过执行 XGROUP SETID命令来为消费者组设置新的最后递送的消息ID:
XGROUP SETID stream group id
命令给定的ID可以是任意合法的消息ID,ID对应的消息可以不必实际存在,并且新ID可以大于,小于,甚至等于当前ID。
需要注意的是:使用 XGROUP SETID 命令显示的修改了最后递送的消息ID,将对后续执行的 XREADGROUP 命令的结果产生影响。
复杂度: O ( 1 ) O(1) O(1)
版本要求:Redis 5.0开始可用。
当用户不再需要某个消费者的时候,可以通过执行以下命令删除该消费者:
XGROUP DELCONSUMER stream group consumer
命令在执行成功后返回一个数字作为结果,这个数字就是消费者被删除时,它仍然在处理的消息数量。
复杂度: O ( N ) O(N) O(N),其中 N N N为被删除消费者正在处理的消息数量。
版本要求:Redis 5.0开始可用。
与上一个命令类似,当一个消费者组完成任务之后,用户可以通过执行以下命令来删除该消费者组:
XGROUP DESTROY stream group
命令执行成功返回 1,失败返回 0.
复杂度: O ( N + M ) O(N+M) O(N+M),其中 N N N为消费者组被删除时,仍处于 待处理状态的消息数量,而 M M M则是属下消费者的数量。
版本要求:Redis 5.0开始可用。
XREADGROUP命令是消费者组版本的XREAD命令,用户可以使用这个命令读取消费者组中的消息:
XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...] id [id ...]
基本用法与XREAD命令一致,区别在于多了一个 GROUP选项。
XREAD命令可以使用特殊值符号 $ 来表示新出现消息的ID,同样的,XREADGROUP命令也有类似的特殊值符号:>,使用该符号命令会自动地向消费者返回尚未递送过的新消息:
127.0.0.1:6379> XREADGROUP GROUP all-message worker1 COUNT 1 STREAMS cgs >
1) 1) "cgs"
2) 1) 13245614555112-0
2) 1) "k2"
2) "v2"
用户可以使用XPENDING命令,获取指定流的指定消费者组目前的待处理消息的相关信息:
XPENDING stream group [start stop count] [consumer]
这些信息包括待处理消息的数量、待处理消息队列中的首条消息和最后一条消息的ID,以及该组名下各个消费者正在处理的消息数量(没有在处理消息的消费者将被省略)。
127.0.0.1:6379> XPENDING cgs all-message
1) (integer) 2
2) 12344566777-0
3) 12344566777-1
4) 1) 1) "worker1"
2) "1"
2) 1) "worker2"
2) "1"
通过执行XACK命令,用户可以将消费者组中的指定消息标记为 “已处理”。被标记的消息将从当前消费者的待处理消息队列中移除,而之后执行的 XREADGROUP命令也不会再读取这些消息:
XACK stream group id [id id ...]
XACK命令在执行之后将返回被标记的消息数量作为结果。
用户可以通过执行XCLAIM命令,将指定消息的归属权从一个消费者转向另一个消费者,这个命令的基本格式并不复杂:
XCLAIM stream group new_consumer max_pending_time id [id id ...]
命令中的stream参数和group参数指定了消息所在的流和消费者组,new_consumer指定了消息的新消费者,而命令中的任意多个id参数则指明了需要转移归属权的消息。
除此之外,命令中毫秒格式的max_pending_time参数指定了执行归属权转移操作所需的最大消息处理时限,具体如下:
默认情况下,XCLAIM命令在成功执行后会把被转移消息的ID及其内容返回给客户端,但是如果有需要的话,用户也可以通过给定可选项 JUSTID,让命令只返回被转移消息的ID,这样处理起来就会更加直观:
XCLAIM stream group new_consumer max_pending_time id [id id ...] [JUSTID]
Redis向用户提供了XINFO命令用于查看流及其消费者组的相关信息,该命令提供了多个具备不同功能的子命令。
XINFO CONSUMERS命令用于打印指定消费者组的所有消费者信息:
XINFO CONSUMERS stream group-name
命令会打印的信息包括消费者的名字,它们正在处理的消息shul数量以及消费者的闲置时长。
XINFO GROUPS命令用于打印与给定流相关联的所有消费者组的信息:
XINFO GROUPS cgs
命令打印的信息包括消费者组的名字、它拥有的消费者数量、组中正在处理消息的数量以及该组最后递送消息的ID。
XINFO STREAM命令用于打印给定流的信息:
XINFO STREAM stream
命令打印的信息包括流的长度(包含的消息数量)、流在底层的基数树表示的相关信息、流相关的消费者组数量、流最后生成的消息的ID以及流的第一个节点和最后一个节点。
上一篇:Redis学习手册8—数据结构之地理坐标
下一篇:Redis学习手册10—数据库操作