必须选stream而非list:当需至少一次投递、多消费者组广播、按id/时间精确查询、消费组内自动负载均衡时;list仅适用于单消费者、允许丢消息、无需回溯的轻量场景。

Redis Stream 是专为消息队列设计的原生数据类型,List 只是“能凑合用”的通用结构;如果你的业务需要至少一次投递、多消费者并行处理、或故障后可追溯重放,就别用 LPUSH + BRPOP 了。
什么时候必须选 Stream 而不是 List
当出现以下任一场景时,List 已经不可靠:
- 消费者处理中途崩溃,你希望消息不丢——
List的RPOP是“取即删”,没 ACK;Stream的XREADGROUP配合XACK才能真正实现“至少一次” - 同一笔订单要触发库存扣减、短信通知、风控扫描三个动作——
List不支持广播;Stream的消费组(XGROUP)天然允许多个独立消费者组各自读取全量消息 - 线上出问题要查“30 分钟前那条支付回调到底有没有发出去”——
List只能从头遍历,且无 ID;Stream支持XRANGE按 ID 或时间范围精确查询 - 消费者实例扩容到 3 个,但你要保证同一条消息只被一个实例处理——
List没有负载均衡语义;Stream消费组内自动分片,每条消息只分配给组内一个消费者
什么情况下 List 还能用
不是所有队列都需要 Kafka 级可靠性。如果满足以下全部条件,List 依然够用且更轻量:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 单消费者 + 单业务线,比如后台定时任务拉取待处理任务列表
- 消息丢失可接受,比如埋点日志、非关键通知
- 没有历史回溯需求,也不需要监控消费进度(
List本身不记录谁读到了哪) - 你已用
BLPOP/BRPOP替代轮询,避免空转 CPU
注意:BRPOP queue 0 的阻塞行为虽好,但它和 RPOP 一样,一旦返回消息就从链表中永久移除——这仍是单次投递模型。
Stream 的坑:漏读和 ID 管理
Stream 并非开箱即用零成本。两个最容易踩的点:
- 消费者第一次启动时没指定起始 ID,会跳过已有消息——必须显式用
0-0或$控制读取起点,例如XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >中的>表示只读新消息,0-0才能读全量 - 消息堆积后,
Stream默认无限增长,可能撑爆内存——要用XTRIM或MAXLEN参数限制长度,比如XADD mystream MAXLEN ~ 1000 * field value - 消费组创建后,若长期无消费者调用
XREADGROUP,Pending List(XPENDING)里的未确认消息不会自动清理,需定期XACK或人工干预
Stream 的复杂度主要在消费组生命周期管理和 ID 语义理解上;而 List 的陷阱藏在“看似简单,实则脆弱”的默认行为里——它不报错,但丢消息时你根本不知道。










