20.Stream消息队列

  • Redis Stream 是 Redis 5.0 版本引入的一种新数据类型,同时它也是 Redis 中最为复杂的数据结构。
  • Stream 实际上是一个具有消息发布/订阅功能的组件,也就常说的消息队列。其实这种类似于 broker/consumer(生产者/消费者)的数据结构很常见,比如 RabbitMQ 消息中间件、Celery 消息中间件,以及 Kafka 分布式消息系统等,而 Redis Stream 正是借鉴了 Kafaka 系统。
  • **优点:**Stream 除了拥有很高的性能和内存利用率外, 它最大的特点就是提供了消息的持久化存储,以及主从复制功能,从而解决了网络断开、Redis 宕机情况下,消息丢失的问题,即便是重启 Redis,存储的内容也会存在。
  • 流程:Stream 消息队列主要由四部分组成,分别是:消息本身、生产者、消费者和消费组。
  • 消费组:一个 Stream 队列可以拥有多个消费组,每个消费组中又包含了多个消费者,组内消费者之间存在竞争关系。当某个消费者消费了一条消息时,同组消费者,都不会再次消费这条消息。被消费的消息 ID 会被放入等待处理的 Pending_ids 中。每消费完一条信息,消费组的游标就会向前移动一位,组内消费者就继续去争抢下一条消息。Redis Stream 消息队列结构程如下图所示:
  • java redis zset 延迟队列 redis stream 延迟队列_Redis

  • 下面对上图涉及的专有名词做简单解释:
  • Stream direction:表示数据流,它是一个消息链,将所有的消息都串起来,每个消息都有一个唯一标识 ID 和对应的消息内容(Message content)。
  • Consumer Group :表示消费组,拥有唯一的组名,使用 XGROUP CREATE 命令创建。一个 Stream 消息链上可以有多个消费组,一个消费组内拥有多个消费者,每一个消费者也有一个唯一的 ID 标识。
  • last_delivered_id :表示消费组游标,每个消费组都会有一个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
  • pending_ids :Redis 官方称为 PEL,表示消费者的状态变量,它记录了当前已经被客户端读取的消息 ID,但是这些消息没有被 ACK(确认字符)。如果客户端没有 ACK,那么这个变量中的消息 ID 会越来越多,一旦被某个消息被 ACK,它就开始减少。
  • **ACK(Acknowledge character)**即确认字符,在数据通信中,接收方传递给发送方的一种传输类控制字符。表示发来的数据已确认接收无误。在 TCP/IP 协议中,如果接收方成功的接收到数据,那么会回复一个 ACK 数据。通常 ACK 信号有自己固定的格式,长度大小,由接收方回复给发送方。

20.1 常用命令

命令

说明

XADD

添加消息到末尾。

XTRIM

对 Stream 流进行修剪,限制长度。

XDEL

删除指定的消息。

XLEN

获取流包含的元素数量,即消息长度。

XRANGE

获取消息列表,会自动过滤已经删除的消息。

XREVRANGE

反向获取消息列表,ID 从大到小。

XREAD

以阻塞或非阻塞方式获取消息列表。

XGROUP CREATE

创建消费者组。

XREADGROUP GROUP

读取消费者组中的消息。

XACK

将消息标记为"已处理"。

XGROUP SETID

为消费者组设置新的最后递送消息ID。

XGROUP DELCONSUMER

删除消费者。

XGROUP DESTROY

删除消费者组。

XPENDING

显示待处理消息的相关信息。

XCLAIM

转移消息的归属权。

XINFO

查看 Stream 流、消费者和消费者组的相关信息。

XINFO GROUPS

查看消费者组的信息。

XINFO STREAM

查看 Stream 流信息。

XINFO CONSUMERS key group

查看组内消费者流信息。

20.2 创建消息ID

  • 创建一个 Srteam 时, 需要创建消息 ID,该 ID 是唯一、不可重复的,并且只增不减。消息 ID 有两种创建方式,一是系统自动生成,二是自定义创建。
  • 1.系统自动创建:语法格式如下:
XADD key ID field value [field value ...]

参数说明如下:

  • key :指定队列名称,如果不存在就创建;
  • ID :消息 id,我们使用*表示由 redis 生成,可以自定义,但是要自己保证递增性;
  • field value :消息记录。
  • 返回值是毫秒时间戳格式的字符串。比如 1670340945721-2,它表示在该毫秒内产生的第 2 条消息。使用示例:
XADD mystream * name u7 age 17
  • 自定义ID:自定义 ID 比较简单,但是需要注意的是 ID 的形式必须是 “整数”,并且后面加入消息的 ID 必须大于前面消息的 ID,也就是自定义 ID 也必须遵守递增的规则。示例如下:
xadd mystream1 5 name uu5 age 15 name uu6 age 16
  • Stream 命令的使用实例:
#添加一个消息, * 表示以时间戳自动创建id
remote:0>xadd mystream * name u1 age 11 name u2 age 12
"1670340945721-0"
remote:0>xadd mystream * name u3 age 13 name u4 age 14
"1670340963560-0"
remote:0>xadd mystream * name u5 age 15 name u6 age 16
"1670340993849-0"

#自定义id等于1,注意id只增不减
remote:0>xadd mystream1 1 name uu1 name 11 name uu2 age 12
"1-0"
remote:0>xadd mystream1 2 name uu1 name 11 name uu2 age 12
"2-0"
#如果插入重复的id号会报错
remote:0>xadd mystream1 2 name uu3 name 13 name uu4 age 14
"ERR The ID specified in XADD is equal or smaller than the target stream top item"
remote:0>xadd mystream1 3 name uu3 name 13 name uu4 age 14
"3-0"
remote:0>xadd mystream1 5 name uu5 name 15 name uu6 age 16
"5-0"
#删除id=2的数据
remote:0>xdel mystream1 2
"1"
#查看stream1队列包含的消息数量,也就消息长度
remote:0>xlen mystream1
"3"
#获取消息列表,-表示最小,+表示最大
remote:0>xrange mystream1 - +
 1)    1)   "1-0"
  2)      1)    "name"
   2)    "uu1"
   3)    "name"
   4)    "11"
   5)    "name"
   6)    "uu2"
   7)    "age"
   8)    "12"


 2)    1)   "3-0"
  2)      1)    "name"
   2)    "uu3"
   3)    "name"
   4)    "13"
   5)    "name"
   6)    "uu4"
   7)    "age"
   8)    "14"


 3)    1)   "5-0"
  2)      1)    "name"
   2)    "uu5"
   3)    "name"
   4)    "15"
   5)    "name"
   6)    "uu6"
   7)    "age"
   8)    "16"

#获取消息列表
remote:0>xrange mystream1 - 5
 1)    1)   "1-0"
  2)      1)    "name"
   2)    "uu1"
   3)    "name"
   4)    "11"
   5)    "name"
   6)    "uu2"
   7)    "age"
   8)    "12"


 2)    1)   "3-0"
  2)      1)    "name"
   2)    "uu3"
   3)    "name"
   4)    "13"
   5)    "name"
   6)    "uu4"
   7)    "age"
   8)    "14"


 3)    1)   "5-0"
  2)      1)    "name"
   2)    "uu5"
   3)    "name"
   4)    "15"
   5)    "name"
   6)    "uu6"
   7)    "age"
   8)    "16"

#使用count指定返回数据的数量
remote:0>xrange mystream1 - 5 count 1
 1)    1)   "1-0"
  2)      1)    "name"
   2)    "uu1"
   3)    "name"
   4)    "11"
   5)    "name"
   6)    "uu2"
   7)    "age"
   8)    "12"

#使用xread读取消息,从id为>2开始
remote:0>xread count 2 streams mystream1 2
 1)    1)   "mystream1"
  2)      1)        1)     "3-0"
    2)          1)      "name"
     2)      "uu3"
     3)      "name"
     4)      "13"
     5)      "name"
     6)      "uu4"
     7)      "age"
     8)      "14"


   2)        1)     "5-0"
    2)          1)      "name"
     2)      "uu5"
     3)      "name"
     4)      "15"
     5)      "name"
     6)      "uu6"
     7)      "age"
     8)      "16"



#使用xread读取消息,从id为>3开始
remote:0>xread count 2 streams mystream1 3
 1)    1)   "mystream1"
  2)      1)        1)     "5-0"
    2)          1)      "name"
     2)      "uu5"
     3)      "name"
     4)      "15"
     5)      "name"
     6)      "uu6"
     7)      "age"
     8)      "16"

#删除整个Stream
remote:0>del mystream

20.3 创建消费组

  • Redis Stream通过XGROUP CREATE指令创建消费组(Consumer Group),在创建时,需要传递起始消息的 ID 用来初始化 last_delivered_id 变量。语法格式如下:
XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]
  • 参数说明如下:
  • key :指定 Stream 队列名称,若不存在则自动创建。
  • groupname :自定义消费组的名称,不可重复。
  • $ :表示从尾部开始消费,只接受新消息,而当前 Stream 的消息则被忽略。
  • 示例:
#创建消费组,并传递消息起始id 0-0
remote:0>xgroup create mystream1 mcg 0-0
"OK"
#从尾部开始消费信息,只接受新消息
remote:0>xgroup create mystream1 mcg1 $
"OK"
#xinfo查看队列信息
remote:0>xinfo stream mystream1
#队列中消息的长度
 1)  "length"
 2)  "3"
 3)  "radix-tree-keys"
 4)  "1"
 5)  "radix-tree-nodes"
 6)  "2"
 7)  "last-generated-id"
 8)  "5-0"
 9)  "max-deleted-entry-id"
 10)  "2-0"
 11)  "entries-added"
 12)  "4"
 13)  "recorded-first-entry-id"
 14)  "1-0"
 #有几个消费组
 15)  "groups"
 16)  "2"
 17)  "first-entry"		#第一个消息
 18)    1)   "1-0"
  2)      1)    "name"
   2)    "uu1"
   3)    "name"
   4)    "11"
   5)    "name"
   6)    "uu2"
   7)    "age"
   8)    "12"


 19)  "last-entry"	 #最后一个消息
 20)    1)   "5-0"
  2)      1)    "name"
   2)    "uu5"
   3)    "name"
   4)    "15"
   5)    "name"
   6)    "uu6"
   7)    "age"
   8)    "16“

remote:0>xgroup destroy mystream1 mcg
"1"

remote:0>xgroup create mystream1 mcg2 0-0
"OK"
#查看消费组信息
remote:0>xinfo groups mystream1
 1)    1)   "name"
  2)   "mcg1"
  3)   "consumers"
  4)   "0"
  5)   "pending"
  6)   "0"
  7)   "last-delivered-id"		#从哪个消息id开始读取
  8)   "5-0"
  9)   "entries-read"
  10)   null
  11)   "lag"
  12)   "0"

 2)    1)   "name"
  2)   "mcg2"
  3)   "consumers"
  4)   "0"
  5)   "pending"
  6)   "0"
  7)   "last-delivered-id"	#从哪个消息id开始读取
  8)   "0-0"
  9)   "entries-read"
  10)   null
  11)   "lag"
  12)   null

20.4 消费消息

  • Redis Stream 通过XREADGROUP命令使消费组消费信息,它和XREAD命令一样,都可以阻塞等待新消息。读到新消息后,对应的消息 ID 就会进入消费者的 PLE(正在处理的消息)结构里,客户端处理完毕后使用 XACK 命令通知 Redis 服务器,本条消息已经处理完毕,该消息的 ID 就会从 PEL 中移除。示意图如下:

java redis zset 延迟队列 redis stream 延迟队列_redis_02

  • XREADGROUP命令的语法格式如下所示:
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]

参数说明如下:

  • group :消费组名称。
  • consumer :消费者名称。
  • count : 要读取的数量。
  • milliseconds : 阻塞时间,以毫秒为单位。
  • key : 键指定的队列名称。
  • ID : 表示消息 ID。
  • 示例如下:
#消费组中消费者读取消息,> 表示每当消费一个信息,消费组游标就前移一位
remote:0>xreadgroup group mcg2 c1 count 1 streams mystream1 >
 1)    1)   "mystream1"
  2)      1)        1)     "1-0"
    2)          1)      "name"
     2)      "uu1"
     3)      "name"
     4)      "11"
     5)      "name"
     6)      "uu2"
     7)      "age"
     8)      "12"
#再使用mcg2读取一条消息
remote:0>xreadgroup group mcg2 c1 count 1 streams mystream1 >
 1)    1)   "mystream1"
  2)      1)        1)     "3-0"
    2)          1)      "name"
     2)      "uu3"
     3)      "name"
     4)      "13"
     5)      "name"
     6)      "uu4"
     7)      "age"
     8)      "14"
#BLOCK 1000表示等待1秒,如果没有任何消息到来,则返回null,此时移动到了末尾
#该换消费组mcg1进行读取消息
remote:0>xreadgroup group mcg1 c1 count 1 block 1000 streams mystream1 >
				#由于mcg1设置的是从mystream1消息队列的末尾开始读取,所以在没有新消息的情况下,mcg1无法读取消息

#再使用mcg2读取一条消息
remote:0>xreadgroup group mcg2 c1 count 1 block 1000 streams mystream1 >
 1)    1)   "mystream1"
  2)      1)        1)     "5-0"
    2)          1)      "name"
     2)      "uu5"
     3)      "name"
     4)      "15"
     5)      "name"
     6)      "uu6"
     7)      "age"
     8)      "16"

#mcg2读取到最后一条消息返回null,此时移动到了末尾,之后就无新消息可读了
remote:0>xreadgroup group mcg2 c1 count 1 block 1000 streams mystream1 >
#指定读取当前读取到的消息id>4
remote:0>xreadgroup group mcg2 c1 count 1 block 1000 streams mystream1 4
 1)    1)   "mystream1"
  2)      1)        1)     "5-0"
    2)          1)      "name"
     2)      "uu5"
     3)      "name"
     4)      "15"
     5)      "name"
     6)      "uu6"
     7)      "age"
     8)      "16"
#超出了消息id的范围id>5
remote:0>xreadgroup group mcg2 c1 count 1 block 1000 streams mystream1 5
 1)    1)   "mystream1"
  2)   
#添加新的消息 ID为7
remote:0>xadd mystream1 7 name uu7 age 17
"7-0"
#使用mcg2消费组读取消息
remote:0>xreadgroup group mcg2 c2 count 2 streams mystream1 >
 1)    1)   "mystream1"
  2)      1)        1)     "7-0"
    2)          1)      "name"
     2)      "uu7"
     3)      "age"
     4)      "17"

#使用mcg1消费组读取消息
remote:0>xreadgroup group mcg1 c2 count 2 streams mystream1 >
 1)    1)   "mystream1"
  2)      1)        1)     "7-0"
    2)          1)      "name"
     2)      "uu7"
     3)      "age"
     4)      "17"
#查看两个消费组信息消费情况
remote:0>xinfo groups mystream1
 1)    1)   "name"
  2)   "mcg1"
  3)   "consumers"
  4)   "2"
  5)   "pending"		#mcg1里的pending里有1消息id需要确认
  6)   "1"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0"

 2)    1)   "name"
  2)   "mcg2"
  3)   "consumers"
  4)   "2"
  5)   "pending"		#pending里存储了4个消息id需要确认
  6)   "4"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0"

#xack将id=5消息标记为已经处理
remote:0>xack mystream1 mcg2 5
"1"
#发现消费组xcg2的pinding带确认消息数下降了一位
remote:0>xinfo groups mystream1
 1)    1)   "name"
  2)   "mcg1"
  3)   "consumers"
  4)   "2"
  5)   "pending"
  6)   "1"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0"

 2)    1)   "name"
  2)   "mcg2"
  3)   "consumers"
  4)   "2"
  5)   "pending"
  6)   "3"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0"

#依次对mcg2组里的带确认消息进行处理,清空mcg2组的pending空间
remote:0>xack mystream1 mcg2 3
"1"
remote:0>xack mystream1 mcg2 1
"1"
remote:0>xinfo groups mystream1
 1)    1)   "name"
  2)   "mcg1"
  3)   "consumers"
  4)   "2"
  5)   "pending"			#只剩id=7的消息待确认
  6)   "1"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0"

 2)    1)   "name"
  2)   "mcg2"
  3)   "consumers"
  4)   "2"
  5)   "pending"			#只剩id=7的消息待确认
  6)   "1"
  7)   "last-delivered-id"
  8)   "7-0"
  9)   "entries-read"
  10)   "5"
  11)   "lag"
  12)   "0

下一篇:redis学习-22- Redis 布隆过滤器(原理+图解)