bigywater.tech
Redis · 48

Stream 与 List 队列对比

Redis 里做消息队列,最常见的两个选择是 List(LPUSH + BRPOP)和 Stream。它们常被拿来比较,但比较要有数据。这篇在同一台实例上量它们的内存、吞吐、延迟,再把两个最本质的差别摆出来:消费者崩溃时消息去哪了、能不能有多个消费方各读一份。

73.5 vs 81.7 B
每条 64 字节消息的内存:List vs Stream
768k vs 510k /s
管道入队吞吐:LPUSH vs XADD
0.19 vs 0.21 ms
阻塞等待到收到的 p50:BRPOP vs XREADGROUP BLOCK

内存

10 万条,64 字节负载
实测 · 本机执行输出
List     7349183 B  (73.5 B/条)  encoding=quicklist
Stream   8173584 B  (81.7 B/条)  (每条含一个字段名 p)

List 用 quicklist 编码,73.5 字节/条;Stream 每条带一个字段名 p,81.7 字节/条,多约 8.2 字节。差别不大;消息更小或字段更多时会不同(见第 39 篇关于字段名的对比)。

入队吞吐

实测 · 本机执行输出
LPUSH    768.4 k 条/秒 (三次: 766k, 768k, 769k)
XADD     509.8 k 条/秒 (三次: 509k, 510k, 512k)

管道 2000 条一批、写 20 万条,三次取中位:LPUSH 768k/s,XADD 510k/s,XADD 慢约 34%。这是单连接管道的上限,不代表真实业务里的差距(真实业务通常受网络和处理逻辑限制)。

消费者崩溃

实测 · 本机执行输出
List:  RPOP 取出 ['j1', 'j2', 'j3', 'j4', 'j5'] → 崩溃后,这 5 条在 Redis 里还找得到吗?  LLEN = 5 (只剩没取的 5 条)
Stream: XREADGROUP 取出 ['1-0', '2-0', '3-0', '4-0', '5-0'] → 崩溃后 XPENDING:
> XPENDING S g
1) (integer) 5
2) "1-0"
3) "5-0"
4) 1) 1) "A"
      2) "5"

List 的 RPOP 是「取出即删除」:取走 5 条、消费者崩溃,LLEN 只剩没取的 5 条,取走的 5 条在 Redis 里再也查不到。Stream 的 XREADGROUP 把这 5 条放进 PEL,崩溃后 XPENDING 立刻能看到 5 条属于 A,可以认领重试。

实测 · 本机执行输出
LMOVE 取出 ['j1', 'j2', 'j3']  崩溃后 L:proc = ['j3', 'j2', 'j1']
(要自己实现:超时检测、谁在处理、重试次数——Stream 的 PEL 里都有:消费者名、idle、delivery count)

List 想做到可靠,要用 LMOVE(取出时同时放进「处理中」列表),处理完再 LREM。可行,但超时检测、认领、重试次数、谁在处理都得自己实现;Stream 的 PEL 已经记录了消费者、空闲时间、投递次数。

多消费方与回看

实测 · 本机执行输出
billing 读到 ['1-0', '2-0', '3-0', '4-0', '5-0']
audit 读到 ['1-0', '2-0', '3-0', '4-0', '5-0']
> XRANGE S 2-0 3-0
1) 1) "2-0"
   2) 1) "job"
      2) "j2"
2) 1) "3-0"
   2) 1) "job"
      2) "j3"
List 弹出即消失:同一条消息只能被一个消费者拿到,读过就不能回看

两个组 billing 和 audit 各自读到全部 5 条,读过的消息还能用 XRANGE 回看;List 的消息一旦被弹出就消失,同一条消息只能被一个消费者拿到。

阻塞等待的延迟

实测 · 本机执行输出
LPUSH → BRPOP                    p50 0.191 ms  p99 0.591 ms  max 1.448 ms
XADD → XREADGROUP BLOCK(不含 XACK) p50 0.208 ms  p99 0.696 ms  max 0.888 ms

消费者阻塞在 BRPOP / XREADGROUP BLOCK,生产者发 500 条各间隔 4 ms,量「发出 → 收到」:BRPOP p50 0.191 ms、p99 0.591 ms;XREADGROUP p50 0.208 ms、p99 0.696 ms。我先前第一次测的时候把 XACK 的往返也算了进去,得到 XREADGROUP 慢一倍的结论;拆开之后两者相近——这里的数字只计到「收到消息」为止,XACK 是额外的一次往返。

分步演示:五个维度的对比

List vs Stream
本步的数据

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。单机回环,生产者与消费者各一个连接;延迟含本机线程调度抖动
    • 没有验证:多消费者并发下的吞吐、消息很大时的差异、List 用 BLMOVE 做可靠队列的完整实现、集群下两者的行为差异、持久化(AOF)开启后的吞吐差别
    京ICP备2026067502号-1京公网安备11010502063487号