Redis学习手册9—数据结构之流

Redis数据结构之流

  • Redis的流
  • Redis流的存储结构
  • 流命令速查表
  • 命令详解
    • XADD命令
      • 流元素的ID
      • 不完整的流ID
      • 流元素ID的限制
      • 自动生成元素ID
      • 限制流的长度
    • XTRIM命令
    • XDEL命令
    • XLEN命令
    • XRANGE 、XREVRANGE命令
      • 对流进行迭代
      • 以逆序的方式访问流
    • XREAD命令
      • 从多个流中获取大于指定ID的元素
      • 迭代流
      • 阻塞
      • 获取新出现的元素
    • 消费者组
    • XGROUP CREATE命令
    • 读取消费者组
    • 消费者
      • 消息的状态转换
  • 消费者组管理
    • 创建消费者组
    • 修改消费者组最后递送的消息ID
    • 删除消费者
    • 删除消费者组
    • XREADGROUP命令
      • 读取未递送的新消息
    • XPENDING命令
    • XACK命令
    • XCLAIM命令
    • XINFO命令
      • 打印消费者信息
      • 打印消费者组信息
      • 打印流消息

Redis的流

(Stream)是Redis 5.0版本中新增的数据结构,也是该版本最重要的更新。在以往的版本中,为了实现消息队列这一常见的应用,用户往往会使用列表,有序集合和发布与订阅这3个功能,但这些不同的实现都有各自的缺陷:

  • 列表实现的消息队列虽然可以快速的将新消息追加到列表的末尾,但因为列表为线性结构,所以程序如果想要查找包含指定数据的元素,或者进行范围查找,就需要遍历整个列表。
  • 有序集合虽然可以有效的进行范围查找,但缺少列表和发布与订阅提供的阻塞弹出原语,这使得程序无法使用有序集合实现可阻塞的消息弹出操作。
  • 发布与订阅虽然拥有将消息传递给多个客户端的能力,并且也拥有相应的阻塞弹出原语,但发布与订阅的 **“发送即忘”**策略会导致离线的客户端丢失消息,所以它是无法实现可靠的消息队列的。

除了以上3种数据结构各自有的问题之外,还有一个问题是3种数据结构共有的:无论是列表、有序集合,还是发布与订阅,它们的元素都只能是单个值。

Redis流的出现解决了上述提到的所有问题,它是上述3种数据结构的综合体,具备它们各自的所有优点以及特点,是使用Redis实现消息队列应用的最佳选择。

是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值对,这些元素会根据ID的大小在流中有序地进行排列。

Redis流的存储结构

下图展示了Redis提供的流在内存中的存储结构:
Redis学习手册9—数据结构之流_第1张图片

流命令速查表

下表列出了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命令

用户可以通过使用XADD命令,将一个带有指定ID以及包含指定键值对的元素追加到流的末尾:

XADD stream id field value [field value ...]

如果给定的流不存在,那么Redis会先创建一个空白的流,然后将给定的元素追加到流中。

流中的每个元素可以包含一个或任意多个键值对,并且同一个流中不同元素可以包含不同数量的键值对。

注意: 流会以有序方式存储用户给定的键值对:用户在创建元素时以什么顺序给定键值对,它们在被取出的时候就是什么顺序。

流元素的ID

流元素的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之外,还可以给出只包含毫秒时间的不完整的流ID:在这种情况下,Redis会自动将ID的顺序编号部分设置为0 :

127.0.0.1:6379> XADD temp-stream 1000000000000 k1 v1
"1000000000000-0"

流元素ID的限制

因为同一个流中的每个元素的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比较规则:

  • 如果新ID的毫秒时间部分比最大ID的毫秒时间部分要大,则允许添加新元素。
  • 如果新ID的毫秒时间部分与最大ID的毫秒时间部分相同,那么对比两个ID的顺序编号部分,如果新ID的顺序编号部分比最大ID的顺序编号部分大,那么允许添加新元素。

由于Redis流对ID的限制,使得Redis从逻辑上将流变成了一种只执行追加操作(append only)的数据结构,这种特性对于使用流实现消息队列和事件系统来说非常重要,因为这可以让用户确信,新的消息和事件只会在已有消息和事件之后。此外,只能将新元素添加到末尾而不允许在数据结构的中间添加新元素,这也是流与列表及有序集合之间的一个显著区别。

自动生成元素ID

Redis对元素ID的要求非常严格,并且还会拒绝不符合规则的ID。为了用户方便执行操作,Redis为XADD命令的ID参数设定了一个特殊值:"*" 当用户使用符号 *作为ID时,Redis将自动为新元素生成一个可用的新ID:

  • 如果在当前毫秒之内还没有出现过任何ID,那么新ID的顺序编号将被设置为 0 。
  • 如果在当前毫秒之内已经存在其他ID,那么这些ID的顺序编号最大的那个加上 1 就是新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版本开始可用。

XTRIM命令

用户除了可以在执行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命令

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命令可以获取流中包含的元素数量:

XLEN stream

复杂度: O ( 1 ) O(1) O(1)
版本要求:Redis 5.0版本开始可用。

XRANGE 、XREVRANGE命令

正如开头所说,流本身是一个有序序列,对于这种序列,使用有序方式获取序列的各个元素是一种非常常见的操作。类似于字符串类型键的LRANGE命令一样,XRANGE命令也可以以遍历或迭代的方式,访问流中的单个或任意多个远:

XRANGE stream start-id end-id [COUNT n]

根据给定的参数不同,XRANGE命令可以实现不同的功能:

  • 如果将起始ID和结束ID设置为同一个ID,那么将获取指定ID的流元素;
  • 如果将起始ID和结束ID设置为不同的ID,那么将获取指定ID范围内的流元素;
  • 如果用特殊值 “-”“+” 来表示最小ID和最大ID,那么可以获取所有流元素;
  • 可选项 COUNT可以用来指定返回流元素的最大数量;

对流进行迭代

可以使用XRANGE命令对流进行迭代,步骤如下:

  • 使用 “-” 作为起始ID,"+" 为结束ID,调用带有 COUNT选项的XRANGE命令,获取流的前 N 个元素。
  • 对于命令返回的最后一个元素,将该元素的ID的顺序部分加 1,得到一个新的ID。
  • 使用新ID作为起始ID,"+" 为结束ID,继续执行上述步骤 1 。
  • 重复步骤 2 和 3,直到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版本开始可用。

XREAD命令

XRANGE命令和XREVRANGE命令不同,XREAD命令只能从一个方向对流进行迭代,但是它提供了更简单的迭代API,支持同时对多个流进行迭代,并且能够以阻塞和非阻塞两种方式执行:

XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...

从多个流中获取大于指定ID的元素

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命令迭代流步骤如下:

  • 将表示流起点的特殊 ID 0-0(或者它的的简写 0)作为ID传入XREAD命令,并通过 COUNT选项读取流最开头的 N 个元素。
  • 使用命令返回的最后一个元素的ID作为参数,再次调用带有COUNT选项的XREAD命令。
  • 重复执行上述两个步骤,直到返回元素的数量小于指定数量为止。

阻塞

通过使用可选项 BLOCK 并给定一个毫秒级精度的超时时间作为参数,能够以可阻塞的方式执行 XREAD命令:

XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 di3 ...

  • 如果在用户给定的流中,有一个或多个流拥有符合条件的、可供读取的元素,那么XREAD命令将直接返回这些元素,而不会进入阻塞状态。
  • 如果给定的所有流中没有符合条件的元素,那么命令将进入阻塞状态。

获取新出现的元素

在以阻塞方式获取流元素的时候,尝尝会出现要获取最新出现的元素的场景,为了解决这个问题,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 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选项,该选项的两个参数分别用于指定被读取消费者组以及负责处理消息的消费者。

消费者组在创建之后,就会跟踪并维护一系列的信息与数据结构,其中包括:

  • 该组属下的消费者名单;
  • 一个队列,记录了该消费者组目前出于 待处理 状态的所有消息,简称待处理消息队列。
  • 该组最后传递的消息的ID。

消费者

从逻辑上说,消费者就是负责处理消息的客户端。与创建消费者组不一样,消费者不用显式的创建,用户只要在执行 XREADGROUP命令时给定的消费者的名字,Redis就会自动为新出现的消费者创建相应的数据结构。

与消费者组一样,消费者也会维护一个属于自己的待处理消息队列:每当用户使用XREADGROUP命令读取一条消息,并将这条消息指派给一个消费者处理时,该消费者就会把所指派的消息添加到自己的待处理消息队列中。

需要注意的是,与多个消费者组能够共享同一个流中的元素不一样,同一消费者组中的每条消息只能有一个消费者,不同消费者将独占组中的不同消息:当一个消费者读取一条消息后,同组的其他消费者将无法读取这条消息。

消息的状态转换

当消费者处理完一条消息之后,它需要向Redis发送一条针对该消息的XACK命令:

XACK stream group id [id id ...]

当Redis接收到消费者发送来的XACK命令之后,就会从消费者组的待处理消息队列以及消费者的待处理消息队列中移除指定的消息。这样一来,这些消息的状态就会从 待处理 转换为 已处理,具体过程如下所示:

  • 首先,当一个生产者通过XADD命令向流中添加一条消息时,该消息就从原来的 “不存在” 状态转换成了 “未递送” 状态。
  • 然后,当一个消费者通过 XREADGROUP命令读取一条消息时,该消息从原来的 “未递送” 状态转换为 “待处理” 状态。
  • 最后,当消费者处理完消息后,并通过 XACK命令向服务器进行确认时,该消息就从原来的 “待处理” 状态转换为 "已确认 状态。

在这里插入图片描述

消费者组管理

创建消费者组

通过执行 XGROUP CREATE命令,用户可以为流创建一个具有指定名称的消费者组:

XGROUP CREATE stream group id

如果给定的流不存在,那么将返回一个错误。

复杂度: O ( 1 ) O(1) O(1)
版本要求:Redis 5.0版本开始可用。

修改消费者组最后递送的消息ID

对于一个已经存在的消费者组来说,用户可以通过执行 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命令

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命令,获取指定流的指定消费者组目前的待处理消息的相关信息:

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命令

通过执行XACK命令,用户可以将消费者组中的指定消息标记为 “已处理”。被标记的消息将从当前消费者的待处理消息队列中移除,而之后执行的 XREADGROUP命令也不会再读取这些消息:

XACK stream group id [id id ...]

XACK命令在执行之后将返回被标记的消息数量作为结果。

XCLAIM命令

用户可以通过执行XCLAIM命令,将指定消息的归属权从一个消费者转向另一个消费者,这个命令的基本格式并不复杂:

XCLAIM stream group new_consumer max_pending_time id [id id ...]

命令中的stream参数和group参数指定了消息所在的流和消费者组,new_consumer指定了消息的新消费者,而命令中的任意多个id参数则指明了需要转移归属权的消息。

除此之外,命令中毫秒格式的max_pending_time参数指定了执行归属权转移操作所需的最大消息处理时限,具体如下:

  • 如果XCLAIM命令执行的时候,消息原来的消费者用在处理该消息上的时间已经超过了指定的时限,那么归属权转移操作就会被执行。
  • 与此相反,如果原消费者处理该消息的时间并未超过给定时限,或者该消息已经被原消费者确认,那么归属权转移操作将放弃执行。

默认情况下,XCLAIM命令在成功执行后会把被转移消息的ID及其内容返回给客户端,但是如果有需要的话,用户也可以通过给定可选项 JUSTID,让命令只返回被转移消息的ID,这样处理起来就会更加直观:

XCLAIM stream group new_consumer max_pending_time id [id id ...] [JUSTID]

XINFO命令

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—数据库操作

你可能感兴趣的:(#,Redis,redis,stream)