消费组读走一条消息,并不意味着它已经处理完了。Redis 会把这条消息记进组里的 Pending Entries List(PEL),等消费者显式 XACK 才移除。PEL 是 Stream 「至少投递一次」语义的核心。这篇量一量它里面到底存了什么、怎么看、什么会让它变大。
> XPENDING s g
1) (integer) 8
2) "1-0"
3) "8-0"
4) 1) 1) "a"
2) "5"
2) 1) "b"
2) "3"XPENDING s g 不带范围时返回汇总:PEL 总条数、最小和最大 ID、每个消费者各有几条(a 5 条、b 3 条)。
> XPENDING s g - + 10 1) 1) "1-0" 2) "a" 3) (integer) 1202 4) (integer) 1 2) 1) "2-0" 2) "a" 3) (integer) 1202 4) (integer) 1 3) 1) "3-0" 2) "a" 3) (integer) 1202 4) (integer) 1 4) 1) "4-0" 2) "a" 3) (integer) 1202 4) (integer) 1 5) 1) "5-0" 2) "a" 3) (integer) 1202 4) (integer) 1 6) 1) "6-0" 2) "b" 3) (integer) 1202 4) (integer) 1 7) 1) "7-0" 2) "b" 3) (integer) 1202 4) (integer) 1 8) 1) "8-0" 2) "b" 3) (integer) 1202 4) (integer) 1
带范围参数时,每条返回 [ID, 消费者, idle 毫秒, 投递次数]。等了 1.2 秒之后,idle 都在 1202 毫秒上下。idle 一直涨,但 Redis 不会自动重新投递——这是留给消费者(或监控)处理的信号。
> XREADGROUP GROUP g b STREAMS s 0
1) 1) "s"
2) 1) 1) "6-0"
2) 1) "job"
2) "j6"
2) 1) "7-0"
2) 1) "job"
2) "j7"
3) 1) "8-0"
2) 1) "job"
2) "j8"
> XPENDING s g - + 10 b
1) 1) "6-0"
2) "b"
3) (integer) 1
4) (integer) 2
2) 1) "7-0"
2) "b"
3) (integer) 1
4) (integer) 2
3) 1) "8-0"
2) "b"
3) (integer) 1
4) (integer) 2用 ID 0(而不是 >)读,返回的是「分配给这个消费者、还没 ack 的历史消息」。观察 b 的三条:重读之后 idle 变成 1 ms、投递次数从 1 变成 2。所以「投递次数」不只是被别人认领时增加,自己重读也会加。
> XPENDING s g IDLE 1000 - + 10 a 1) 1) "1-0" 2) "a" 3) (integer) 1203 4) (integer) 1 2) 1) "2-0" 2) "a" 3) (integer) 1203 4) (integer) 1 3) 1) "3-0" 2) "a" 3) (integer) 1203 4) (integer) 1 4) 1) "4-0" 2) "a" 3) (integer) 1203 4) (integer) 1 5) 1) "5-0" 2) "a" 3) (integer) 1203 4) (integer) 1
XPENDING 可以加 IDLE 过滤,只看空闲超过某个毫秒数的条目,用来找「卡住」的消息。
> XPENDING s g2 1) (integer) 0 2) (nil) 3) (nil) 4) (nil)
g2 组读了 4 条,NOACK 让它们不进 PEL,XPENDING 显示 0。没有 PEL 就没有重投机制:消费者崩溃就丢了这几条。
> XDEL s 1-0
(integer) 1
> XPENDING s g - + 10 a
1) 1) "1-0"
2) "a"
3) (integer) 1204
4) (integer) 1
2) 1) "2-0"
2) "a"
3) (integer) 1204
4) (integer) 1
3) 1) "3-0"
2) "a"
3) (integer) 1204
4) (integer) 1
4) 1) "4-0"
2) "a"
3) (integer) 1204
4) (integer) 1
5) 1) "5-0"
2) "a"
3) (integer) 1204
4) (integer) 1
> XREADGROUP GROUP g a STREAMS s 0
1) 1) "s"
2) 1) 1) "1-0"
2) (nil)
2) 1) "2-0"
2) 1) "job"
2) "j2"
3) 1) "3-0"
2) 1) "job"
2) "j3"
4) 1) "4-0"
2) 1) "job"
2) "j4"
5) 1) "5-0"
2) 1) "job"
2) "j5"把 1-0 XDEL 之后,PEL 里的 1-0 还在(idle 继续涨);用 XREADGROUP … 0 重读时,1-0 的内容是 (nil)——ID 还在 PEL,消息已经不在流里。消费者得处理这种「有 ID 没内容」的情形。
读之前 2447454 B;读完(PEL 100000 条)后 11875281 B;PEL 增加 9427827 B,平均 94.3 B/条
10 万条消息读走不 ack:MEMORY USAGE 从 2447454 B 涨到 11875281 B,PEL 增加约 94.3 字节/条。第 43 篇会看到 ack 之后这部分内存会还回去。