bigywater.tech
Redis · 40

Stream Entry ID

Stream 里每条消息都有一个 ID,形如 1790692351341-0。它不只是个编号:它决定了消息的顺序、范围查询怎么写、消费者从哪里继续读。这篇把 ID 的规则一条条在真实 Redis 上验证:自动生成、同毫秒序号、显式指定的限制、时钟回拨、范围写法、上限,以及 XDEL 之后 ID 会不会回收。

1141
管道连发 5000 条 XADD 时,同一毫秒里最多的条数(序号 0…1140)
64 + 64 位
毫秒与序号各 64 位;用满后 XADD * 直接报错
永不回退
即使 XDEL 了最后一条、或 ID 被造成「未来」

自动生成:毫秒 + 序号

实测 · 本机执行输出
> XADD a * i 0
"1790692351341-0"
> XADD a * i 1
"1790692351341-1"
> XADD a * i 2
"1790692351341-2"

ID 的前半是服务器当前的毫秒时间戳,后半是序号。同一毫秒里的多条消息靠序号区分。

实测 · 本机执行输出
5000 条落在 6 个不同毫秒;同一毫秒最多 1141 条(序号 0..1140)

为了看序号能加多高,把 5000 条 XADD 用管道一次性发出:这些消息只落在 6 个不同毫秒里,最挤的一毫秒有 1141 条,序号 0 到 1140。这个数字取决于这台机器的写入速度,在别的机器上会不同。

显式指定 ID:必须严格递增

实测 · 本机执行输出
> XADD e 1000-1 k v
"1000-1"
> XADD e 1000-1 k v
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
> XADD e 1000-0 k v
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
> XADD e 999-99 k v
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
> XADD e 0-0 k v
(error) ERR The ID specified in XADD must be greater than 0-0
> XADD e 1000-2 k v
"1000-2"
> XADD e 1001-* k v
"1001-0"
> XADD e 1001-* k v
"1001-1"

规则:新 ID 必须严格大于流里当前最大的 ID(等于也不行);0-0 永远不合法。可以只指定毫秒、序号写 *:1001-* 第一次得到 1001-0,第二次 1001-1。

时钟被拨回去了会怎样

实测 · 本机执行输出
> XADD fut 1790695951356-0 k v
"1790695951356-0"
> XADD fut * k v
"1790695951356-1"
> XADD fut * k v
"1790695951356-2"
(当前毫秒时间戳 1790692351356;last-generated-id 比它大 3600000 ms,* 生成的 ID 一直沿着最后一个 ID 的 seq 往上加)

我没有真的去改系统时钟,而是用等价的办法:先写一条「未来一小时」的 ID,再用 * 追加。结果两条新消息分别是 1790695951356-1、1790695951356-2——服务器没有用当前时间(比它小),而是沿着最后一个 ID 的毫秒继续增加序号。这说明 * 的规则是「取当前时间与最后 ID 中较大的,毫秒相同则序号加 1」,所以 ID 在一个 Stream 内永远单调递增。代价:一旦某次写入用了很大的显式 ID,后面所有自动 ID 都会被它「拖着走」。

范围查询里 ID 怎么写

实测 · 本机执行输出
> XADD r 10-0 v a
"10-0"
> XADD r 20-0 v b
"20-0"
> XADD r 20-1 v c
"20-1"
> XADD r 30-0 v d
"30-0"
> XRANGE r 20 20
1) 1) "20-0"
   2) 1) "v"
      2) "b"
2) 1) "20-1"
   2) 1) "v"
      2) "c"
> XRANGE r (20-0 +
1) 1) "20-1"
   2) 1) "v"
      2) "c"
2) 1) "30-0"
   2) 1) "v"
      2) "d"
> XRANGE r - 20-0
1) 1) "10-0"
   2) 1) "v"
      2) "a"
2) 1) "20-0"
   2) 1) "v"
      2) "b"
> XREVRANGE r + - COUNT 2
1) 1) "30-0"
   2) 1) "v"
      2) "d"
2) 1) "20-1"
   2) 1) "v"
      2) "c"

只写毫秒(20)时,起点补 -0、终点补最大序号,所以 XRANGE r 20 20 命中 20-0 和 20-1 两条。前面加 ( 表示排除这个 ID;- 和 + 是最小和最大;XREVRANGE 的起止参数顺序反过来(先写大的)。

上限与 XDEL

实测 · 本机执行输出
> XADD max 18446744073709551615-18446744073709551615 k v
"18446744073709551615-18446744073709551615"
> XADD max * k v
(error) ERR The stream has exhausted the last possible ID, unable to add more items
> XADD max 18446744073709551616-0 k v
(error) ERR Invalid stream ID specified as stream command argument

毫秒和序号各是 64 位无符号整数;把 ID 写成两个都是 2⁶⁴−1 之后,再用 * 追加直接报错;超过 64 位的数字被当成非法 ID。

实测 · 本机执行输出
                  length = 2
       last-generated-id = 7-0
    max-deleted-entry-id = 7-0
           entries-added = 3
 recorded-first-entry-id = 5-0
> XADD i 7-0 k v
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
(XDEL 不会让 last-generated-id 回退,被删掉的 7-0 也不能再用)

XDEL 删掉了 7-0,length 从 3 变 2,但 entries-added 仍是 3,last-generated-id 和 max-deleted-entry-id 都是 7-0,再写 7-0 被拒绝。ID 一旦发出就不会回收——这也是消费者可以靠「上次读到哪个 ID」续读的前提。

分步演示:ID 的规则

每一步对应上面的一段真实输出
Stream Entry ID
本步涉及的 ID

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动
    • 没有验证:真实的系统时钟回拨(用「未来 ID」等价模拟,没有改系统时间)、集群/复制下 ID 的生成差异、Redis 8 新增的幂等相关字段(XINFO 里的 idmp-*)。同一毫秒最多条数取决于机器速度
    京ICP备2026067502号-1京公网安备11010502063487号