Redis——Stream

Redis —— Stream

Stream是Redis从5.0后加入的新的数据类型。它以更抽象的方式对日志数据进行建模。但是,日志的本质依然完好无损

XADD命令

> XADD mystream * name myname age 24
1615431119174-0

XADD 命令是用来添加一个条目到指定的key,上面命令中的 含义:

  • mystream: stream key
  • “*”:id id可以自己指定,但常用和推荐使用“ * ”来自动生成ID
  • name/age:字段(field)
  • myname/24:字段对应的值(value)

插入成功后会返回该条目自动生成的ID。

条目ID

添加成功后返回的ID它由俩部分组成:

<millisecondsTime>-<sequenceNumber>

前面的 millisecondsTime 是 Redis 服务器本地时间。sequenceNumber 表示同一毫秒内创建的条目数。由于序列号的宽度是64 bit 因此同一毫秒内生成的条目数没有限制。

为什么时间是ID的一部分?

因为 Redis Stream 支持按 ID 进行范围查询。由于ID和时间有关,所以可以轻松的通过时间范围来查询条目,在 XRANGE 命令中可以很好的体现。

当然了,如果你需要你的 ID 与外部系统的ID有关联,那么可以不适用“ * ” 自动生成ID,而是显示的指定 ID。像下面这样:

> XADD somestream 0-1 field1 value1
0-1
> XADD somestream 0-2 field2 value2
0-2

注意:如果使用了自定义ID的添加方式,例如上面的例子中0-1,那么在新增下一条的时候的 ID 不能等于或小于0-1。

> XADD somestream 0-1 field3 value3
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item

XLEN命令

> XLEN mystream
(integer) 1

该命令返回指定stream中的条目条数。

查询数据

主要有三种查询方式

通过范围查询(XRANGE 和 XREVRANGE)

通过范围查询条目我们只需要提供俩个ID——开始ID和结束ID。这里返回的内容包含起始ID和结束ID的值。俩个特殊的ID:

  • “ - ”:表示最下ID
  • “ + ”:表示最大ID
> XRANGE mystream - +
返回该 stream 下所有条目

查询返回的结构如下:

1) item-ID
2) 1) "field1"
   2) "value1"
   3) "field2"
   4) "value2"

如果ID是通过*自动生成的ID,可以通过来个毫秒范围查询该范围内的数据。

> XRANGE mystream 1518951480106 1518951480107
1) 1) 1518951480106-0
   2) 1) "sensor-id"
      2) "1234"
      3) "temperature"
      4) "19.8"

在实际生产中一个时间范围内的数据可能是巨大的,XRANGE 提供了COUNT可选参数,指定COUNT来控制返回的条目数,例如想得到上述时间内的2条数据可以通过一下命令获取:

> XRANGE mystream 1518951480106 1518951480107 COUNT 2

XRANGE的复杂度是O(log(n))

XREVRANGE 与XRANGE 是等效的,只不过 XREVRANGE 是以相反的顺序返回结果,所以它的用法是检查Stream中最后一项是什么:

> XREVRANGE mystream + - COUNT 1
返回stream中最后一条数据

注意:在XREVRANGE中的开始ID和结束ID的参数顺序也是相反的。

使用XREAD监听新条目

如果不想通过范围去查询条目,而是想接收stream中最新的条目数据,这个时候就需要用到XREAD来处理。这里的概念类似与Pub/Sub。但是使用stream方式实现有根本的差异:

  • 一个stream可以有多个客户端(消费者)等待数据。每个新的条目默认都会发送给等待该stream数据的每一个消费者。这里与阻塞列表不同,阻塞列表中每个消费者会获取到不同的条目。但是它这种能将数据发送给多个消费者的能力与Pub/Sub相似。
  • 与Pub/Sub(发布/订阅)不同的是,Pub/Sub 的消息在被触发后就会被丢弃,不会永久存储。在阻塞列表中,当用户收到一条消息后,该消息就会从列表移除(有效删除),但 stream 不是这样的。所有被添加到stream中的数据是无限期的(除非用户主动删除)。通过记住上一条的ID,用户也能分辨出那些消息是新消息。
  • stream中增加了组(Group)的概念,这是Pub/Sub 和阻塞列表无法实现的控制级别。针对同一stream中不同的组有以下能力:显示确认已处理项目、检查待处理项目、声明未处理消息,以及每个用户的连贯历史可见性(仅能查看其过去的私人消息记录)。

XREAD 相对 XRANGE 较为复杂,这里先从简单的命令开始。

> 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"

上面的这种获取方式是XREAD的非阻塞形式,COUNT 参数是可选参数,整条命令里只有STREAMS是强制要求的,它指定了要操作的stream key 和 一个指定的ID ,查出的消息列表起始ID将大于指定ID,这里ID 设为了0,也就是将读取stream中ID大于 0-0 的所有数据。

从上面返回的结果可以看出它多了一个字段用来显示该消息属于哪个stream,为什么要现实呢?因为XREAD命令可以同时读取多个stream的数据,所以它是为了区分消息对应的哪个stream。例如:

> XREAD COUNT 2 STREAMS mystream1 mystream2 0 0

截至到上面的使用,看起来与XRANGE没有太大的区别,除了它可以读取多个流并且指定从哪个ID开始读取。但是XREAD可以通过BLOCK参数轻松的将XREAD转换为阻塞命令。

> XREAD BLOCK 0 STREAMS mystream $

上面的命令从移除了COUNT参数,因为它是可选的,新加了BLOCK参数,它后面的0是代表阻塞超时时间,这里设置为0表示永不超时。并且这里再stream名称后面也没有使用常规的ID,而是使用了特殊ID——$,这个特殊的符号表示应该使用已经存储在stream中最大的ID作为最后一个ID,因此仅能接收到最新的消息。

当然了,并不是一定要使用$特殊的ID,这里可以使用任意合法的ID,但是注意,如果指定的ID小于目前最大的ID,则不会阻塞,而是直接返回大于该ID的所有条目,例如:

> XREAD BLOCK 0 STREAMS mystream 0

上面这条语句不会阻塞,它会直接返回ID大于0的所有条目。如果想验证以下指定ID阻塞可以指定ID来验证

> XADD mystream2 1-0 type java used 90%
> XADD mystream2 2-0 type javascript used 90%
添加上面俩条数据到mystream2中,然后再打开一个窗口执行
> XREAD BLOCC 0 STREAMS mystream2 3-0
执行完上述命令后会发生阻塞,然后返回第一个窗口继续添加
> XADD mystream2 3-0 type python used 50%
这时候第二个窗口依然会阻塞,因为新添加的条目的ID没有大于3-0,而是等于3-0,然后继续添加
> XADD mystream2 4-0 type html used 90%
这时候第二个窗口收到ID为4-0的消息,阻塞结束

同样带有BLOCK参数的XREAD命令也可以监听多个stream。

> XADD BLOCK 0 STREAMS mystream1 mystream2 $ $

消费者组(Consumer Groups)

当处理的任务是来自不同客户端的相同的stream时,XREAD本身提供了一种扇形发送的方式,也就是说每个客户端都会收到完全相同的stream内容。但在一些情况下我们不希望不同的客户端收到相同的消息流,而是从同一个stream中提供不同的消息流给不同的客户端。

下面我们来想象一个问题,现在有3个客户端 C1、C2、C3 和一个stream,这个stream中包含消息:1、2、3、4、5、6、7 我们想根据小面的对应关系让客户端收到消息

1 -> C1
2 -> C2
3 -> C3
4 -> C1
5 -> C2
6 -> C3
7 -> C1

为了实现上面的这种需求,Redis Stream 中使用了一个重要的概念——消费群组(Consumer Groups),理解消费群组时非常重要的 ,使用群组要满足以下条件:

  • 每个消息都提供给不同的消费者,因此,不能将同一消息传递给多个消费者。
  • 每个消费者在群组中都有一个属于自己的名称(name),该字符串区分大小写。这个字符串时必须要有的。这意味着即使断开连接,消费者组也将保留现有的状态,因为客户端会再次声称自己是同一个消费者。但是这也意味着由客户端提供标识。
  • 每个使用者组都具有第一个从未使用过的ID的概念,因此,当使用者请求新消息时,它可以仅提供以前未传递的消息。
  • 消费一条消息需要使用特定的命令进行明确的确认。 Redis发出确认消息的原因是:该消息已正确处理,因此可以从消费者组中撤出
  • 使用者组跟踪当前所有待处理的消息,即已传递给该使用者组的某些使用者但尚未被确认为已处理的消息。借助此功能,在访问流的消息历史记录时,每个使用者只会看到传递给它的消息。

消费者组结构

+----------------------------------------+
| consumer_group_name: mygroup           |
| consumer_group_stream: somekey         |
| last_delivered_id: 1292309234234-92    |
|                                        |
| consumers:                             |
|    "consumer-1" with pending messages  |
|       1292309234234-4                  |
|       1292309234232-8                  |
|    "consumer-42" with pending messages |
|       ... (and so forth)               |
+----------------------------------------+

有关消费者组的命令:

  • __XGROUP: __ 用于创建、销毁和管理消费群组
  • __XREADGROUP: __ 用于通过用户组从流中读取数据
  • __XACK: __ 允许使用者将挂起的消息标记为已读
创建群组
> XGROUP CREATE mystream1 mygroup1 $ 

当创建消费群组时我们需要指定一个ID,这里又是特殊ID—— , 它 的 含 义 和 上 面 的 ,它的含义和上面的 相同,即最后一个消息的ID,如果指定了$,那么现在只stream中到达了新的消息时才会给消费群组中的消费者,如果改为0则群组将使用历史记录中的所有消息。当然也可以指定任意的有效ID值。

XGROUP CREATE 也支持可选参数MKSTREAM 它的作用是在创建群组时如果指定的stream不存在则自动创建该stream。

> XGROUP CREATE newstream newgroup $ MKSTREAM
通过群组读取消息
> XREADGROUP GROUP mygroup1 huhailong count 1 streams mystream >

读取时通过命令__XREADGROUP__ 命令来读取的,它和XREAD命令是相似的,这里多了参数GROUP,它用来指定群组名称和消费者名称,这里要注意最后的特殊ID—— “ > ” 它表示将获取迄今为止为传递给其他消费者的新消息,并且它也会更新消费者组的最后一个ID。我们可以把这个特殊的ID替换为任意合法的ID,例如替换为0的话那么它将会访问第一条消息,这个类似于查看历史记录的意思。

但是如果我们将消息进行确认处理,那么该消息就不再是历史记录里的消息了,因此也就读取不到了,例如我们ack了第一条消息——ID为1615478026546-0的消息,那么再通过指定0去读取是获取不到数据的。

XACK mystream mygroup 1615478026546-0

XACK 命令就是用来确认消息的。

注意: 同一个组中中的消息只能被一个消费者消费,但一个stream可以有多个组,每个组都能收到stream的消息。例如有俩个组G1和G2,它们都是同一个stream下的组,那么当stream追加一条消息时,G1和G2都会收到消息,但每个组中仅能有一个消费者消费该消息。

使用组应该注意一下几点:

  • 消费者在第一次消费时自动创建,不需要显示的去创建,例如上面huhailong在消费时没有显示的指定消费者时huhailong,而是在消费时自动创建。

  • 使用XREADGROUP也可以同时读取多个stream key,但是前提是每个stream中要创建一个相同名称的group,

    > XGROUP CREATE newstream mygroup1 $
    > XREADGROUP GROUP mygroup1 huhailong STREAMS mystream newstream > >
    

    像上面这样,在newstream中也创建一个名称为mygroup1的组,这时候给mystream或者newstream追加新消息时通过上面的方式都会读取到。

  • XREADGROUP是一个写入命令,因为即使它从流中读取,消费组也会被修改为读取的连带影响,因此只能在主实例上调用它。

从永久性故障中恢复

在Redis Stream 中,每个消费者都要处理一个子集的消息,并且在从故障中恢复时,重新只读取传递给自己的挂起消息。但在真实环境中消费者可能从故障中永远无法恢复,那么他们挂起的消息发生了什么?

Redis用户组提供了一种在这些情况下使用的功能,用于声明给定用户的挂起消息,以便这些消息将更改所有权并重新分配给不同的用户。功能非常明确。使用者必须检查挂起消息的列表,并且必须使用特殊命令声明特定消息,否则服务器将永远挂起消息并将其分配给旧使用者。通过这种方式,不同的应用程序可以选择是否使用这样的功能,以及具体如何使用它。

XPENDING 命令

该命令用于观察消费组挂起消息的信息。

> XPENDING mystream mygroup
1) (integer) 5
2) "1615515152321-0"
3) "1615517707878-0"
4) 1) 1) "hhl"
      2) "5"
> XPENDING mystream mygroup - + hhl
1) 1) "1615515152321-0"
   2) "hhl"
   3) (integer) 1937035
   4) (integer) 5
2) 1) "1615515821721-0"
   2) "hhl"
   3) (integer) 17716
   4) (integer) 1
3) 1) "1615516301400-0"
   2) "hhl"
   3) (integer) 17716
   4) (integer) 1
4) 1) "1615516308252-0"
   2) "hhl"
   3) (integer) 17716
   4) (integer) 1
5) 1) "1615517707878-0"
   2) "hhl"
   3) (integer) 17716
   4) (integer) 1

上面时该命令的使用方法以及返回信息,在不指定 start 和 end 时,返回的概括信息

  • 挂起消息总数
  • 挂起消息中最小ID
  • 挂起消息中最大ID
  • 挂起消息对应消费者名称以及对应该消费者的挂起条数

当给定返回,返回条数,消费者名称,这三个都是可选的参数,返回的时详细信息

  • 挂起消息的ID
  • 挂起消息对应的消费者名称
  • 该消息闲置时间(单位:毫秒)

注意:当消息被ACK后就不会被挂起

XCLAIM 命令

用于更改挂起消息所属的消费者,也就是将消息的更改所有权交给指定的消费者。

> XCLAIM mystream mygroup zxc 3600000 1526569498055-0

但是要注意的是,执行完上面的命令后对应ID消息的空闲时间会被重置。

可以通过可选参数JUSTID来设置XCLAIM只返回成功后消息的ID,这对减少带宽或对消息的其他信息不感兴趣是有用的。

Automatic claming

在__Redis6.2__中引入了__XAUTOCLAIM__ 命令

redis6.2中添加的XAUTOCLAIM命令实现了我们上面描述的声明过程。XPENDING和XCLAIM为不同类型的恢复机制提供了基本的构建块。这个命令通过让Redis管理通用流程来优化它,并为大多数恢复需求提供了一个简单的解决方案。

XAUTOCLAIM标识空闲的挂起消息并将其所有权转移给使用者。命令的签名如下所示:

XAUTOCLAIM      [COUNT count] [JUSTID]

你可能感兴趣的:(随笔,redis)