数据结构之stream
流是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值对,这些元素会根据ID的大小在流中有序地进行排列,是使用Redis实现消息队列应用的最佳选择。
XADD:追加新元素到流的末尾
XADD stream id field value [field value ...]
流元素会以有序方式存储用户给定的键值对。
流元素的ID由毫秒时间(millisecond)和顺序编号(sequcen number)两部分组成,其中使用UNIX时间戳表示的毫秒时间用于标识与元素相关联的时间,而以0为起始值的顺序编号则用于区分同一时间内产生的多个不同元素。因为毫秒时间和顺序编号都使用64位的非负整数表示,所以整个流ID的总长为128位,而Redis在接受流ID输入以及展示流ID的时候都会使用连字符-分割这两个部分。
用户在输入流ID的时候,除了可以给出带有毫秒时间和顺序编号的完整流ID之外,还可以给出只包含毫秒时间的不完整流ID:在这种情况下,Redis会自动将ID的顺序编号部分设置为0。
因为同一个流中的每个元素ID都用于指定特定的一个元素,所以这些ID必须是各不相同的,换句话说,同一个流中的不同元素是不允许使用相同ID的。
除了不允许使用相同的ID之外,Redis还要求新元素的ID必须比流中所有已有元素的ID都要大。
如果新ID的毫秒时间部分比最大ID的毫秒时间部分要大,那么允许添加新元素。
如果新ID的毫秒时间部分与最大ID的毫秒时间部分相同,那么对比两个ID的顺序编号部分,如果新ID的顺序编号部分比最大ID的顺序编号部分要大,那么允许添加新元素。
不符合上述两种情况的添加操作将会被拒绝。
只能将新元素添加到末尾而不允许在数据结构的“中间”添加新元素,这也是流与列表以及有序集合之间的一个显著区别。
当用户将符号*用作id参数的值时,Redis将自动为新添加的元素生成一个可用的新ID。
具体来说,自动生成的新ID会将Redis所在宿主机器当前毫秒格式的UNIX时间戳用作ID的毫秒时间,并根据当前已有ID的最大顺序编号来设置新ID的顺序编号部分: ·如果在当前毫秒之内还没有出现过任何ID,那么新ID的顺序编号将被设置为0。比如,如果当前时间为1530200000000ms,并且在这一毫秒之内没有任何现存的ID,那么新元素的ID将被设置为1530200000000-0。 ·如果在当前毫秒内已经存在其他ID,那么这些ID中顺序编号最大的那个加上1就是新ID的顺序编号。比如,如果在1530200000000ms内,现存最大的顺序编号为10086,那么新ID的顺序编号将是10086加1的结果10087,而完整的新ID则是1530200000000-10087。
如果用户使用了*作为ID参数的值,但是宿主机器的当前时间比流中已有最大ID的毫秒时间要小,那么Redis将使用该ID的毫秒时间来作为新ID的毫秒时间,以此来避免机器时间倒流产生错误。
XTRIM:对流进行修剪
XTRIM命令与带有MAXLEN选项的XADD命令一样,都是根据先进先出规则来淘汰旧元素的。
XDEL:移除指定元素
XDEL命令接受一个流以及任意多个元素ID作为输入,并从流中移除ID对应的元素。
XLEN:获取流包含的元素数量
XRANGE、XREVRANGE:访问流中元素
XRANGE命令最简单的用法就是将命令的起始ID和结束ID设置为同一个流元素ID,这样XRANGE命令就会从流中获取并返回ID指定的元素。
用户只需要将一个较小的元素ID设置为命令的起始ID,并将一个较大的元素ID设置为命令的结束ID,那么XRANGE命令就会从流中获取从起始ID到结束ID区间范围内的所有元素。
XRANGE命令的起始ID和结束ID除了可以是流元素的ID之外,还可以是特殊值减号-和加号+,其中前者用于表示流中的最小ID,而后者则用于表示流中的最大ID。在这两个特殊值的帮助下,用户可以通过XRANGE命令获取流包含的所有元素,或者ID大于等于或小于等于指定ID的所有元素。
用户可以通过XRANGE命令的COUNT选项去限制一次命令调用能够返回的最大元素数量。
XREAD:以阻塞或非阻塞方式获取流元素
XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...
从多个给定流中取出大于指定ID的多个元素,其中紧跟在STREAMS选项之后的就是流的名字以及与之相对应的元素ID
XREAD STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...
通过使用BLOCK选项并给定一个毫秒精度的超时时间作为参数,用户能够以可阻塞的方式执行XREAD命令
XREAD [BLOCK ms] [COUNT n] STREAMS stream1 stream2 stream3 ... id1 id2 id3 ...
如果用户在执行带有BLOCK选项的XREAD命令时,给定的所有流都不存在可供读取的元素,那么命令将进入阻塞状态。如果在给定的阻塞时长之内有一个可供读取的元素出现,那么Redis将把这个元素分发给所有因为该元素而被阻塞的客户端,这种情况下的XREAD命令会无视用户给定的COUNT选项,只返回一个元素。
Redis为XREAD命令提供了特殊ID参数符号用作ID参数的值,XREAD命令就会只获取给定流在命令执行之后新出现的元素
XREAD BLOCK ms STREAMS stream1 stream2 stream3 ... $ ...
XGROUP: 消费者组
Redis流的消费者组(consumer group)允许用户将一个流从逻辑上划分为多个不同的流,并让消费者组属下的消费者去处理组中的消息。
创建消费者组
XGROUP CREATE stream group start_id
命令中的stream参数用于指定流的名字,group参数用于指定将要创建的消费者组的名字。此外,start_id参数用于指定消费者组在流中的起始ID,这个ID决定了消费者组要从流的哪个ID之后开始进行读取。
XREADGROUP: 读取消费者组
XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream [stream ...]
id [id ...]
消费者组在创建之后就会跟踪并维护一系列信息和数据结构,其中包括:
·该组属下的消费者名单。
·一个队列,记录了该组目前处于“待处理”状态的所有消息,简称待处理消息队列。
·该组最后递送的消息的ID。
消费者
需要注意的是,与多个消费者组能够共享同一个流中的元素不一样,同一消费者组中的每条消息只能有一个消费者,换句话说,不同的消费者将独占组中的不同消息:当一个消费者读取了组中的一条消息之后,同组的其他消费者将无法读取这条消息。
当消费者处理完一条消息之后,它需要向Redis发送一条针对该消息的XACK命令:
XACK stream group id [id id ...]
XGROUP:管理消费者组
创建消费者组
XGROUP CREATE stream group id
对于一个已经存在的消费者组来说,用户可以通过执行XGROUP SETID命令来为消费者组设置新的最后递送消息ID:
XGROUP SETID stream group id
删除消费者
XGROUP DELCONSUMER stream group consumer
需要注意的是,当消费者被删除之后,它在被删除时处理的消息也会从消费者组的待处理消息队列中移除。换句话说,属于被删除消费者的待处理消息将不再处于“待处理”状态,这些消息可能已经被消费者处理掉了,但也可能尚未得到妥善的处理。 为了避免这个问题,用户在删除一个消费者之前应该确保递送给它的所有消息均已处理完毕,或者使用XCLAIM命令显式地转移待处理消息的归属权。换句话说,为了保证程序的正确性,用户应该保证每个XGROUP DELCONSUMER命令的返回值都为0。
删除消费者组
XGROUP DESTROY stream group
XREADGROUP:读取消费者组中的消息
XREADGROUP命令在读取消息的同时,还会将该消息分别添加到消费者组的待处理消息队列以及消费者的待处理消息队列中,从而使得被读取消息的状态从原来的“未递送”转变成“待处理”。
XREADGROUP命令除了会把被读取的消息添加到上述两个队列之外,还会将最后一条被读取的消息的ID设置成消费者组的最后递送消息ID。
消费者组的待处理消息队列记录了所有已经被递送但是尚未被确认的待处理消息,而每个消费者各自专属的待处理消息队列则记录了各个消费者所属的待处理消息,这两个队列的存在使得Redis不会错误地将同一条消息递送给不同的消费者,也可以让用户在处理完一条消息之后,通过XACK命令对其进行确认。至于最后递送消息ID的存在则保证了消费者组只会向消费者递送新出现的消息,而不会重复地递送已经递送过的旧消息(除非用户显式地要求进行这一操作)。
只要将id参数的值设置为特殊符号>,命令就会自动地向消费者返回尚未递送过的新消息。
XPENDING:显示待处理消息的相关信息
XACK:将消息标记为“已处理”
XCLAIM:转移消息的归属权
XINFO:查看流和消费者组的相关信息
XINFO CONSUMERS命令用于打印指定消费者组的所有消费者,以及这些消费者的相关信息。
XINFO GROUPS命令用于打印与给定流相关联的所有消费者组,以及这些消费者组的相关信息。
XINFO STREAM命令用于打印给定流的相关信息。