Stream 里每条消息都有一个 ID,形如 1790692351341-0。它不只是个编号:它决定了消息的顺序、范围查询怎么写、消费者从哪里继续读。这篇把 ID 的规则一条条在真实 Redis 上验证:自动生成、同毫秒序号、显式指定的限制、时钟回拨、范围写法、上限,以及 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。这个数字取决于这台机器的写入速度,在别的机器上会不同。
> 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 都会被它「拖着走」。
> 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 的起止参数顺序反过来(先写大的)。
> 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」续读的前提。