4. Redis Stream 详解:数据结构、消费者组与消息确认机制

Redis Stream 是 Redis 5.0 引入的原生消息队列数据结构,它解决了 Pub/Sub 无持久化、List 消息确认困难、ZSet 无阻塞消费等核心痛点。Stream 以日志追加模型存储消息,支持消费者组实现消息的负载均衡与可靠投递,是构建生产级消息队列的最佳选择。

4.1 Stream 核心数据结构

Stream 本质上是一个仅追加的日志,每条消息由唯一的 ID 和 键值对 组成。消息 ID 默认由服务器自动生成,格式为 <毫秒时间戳>-<序列号>,保证全局有序且唯一。

概念 说明 示例
消息 ID 全局唯一、单调递增,支持按 ID 范围查询 1712345678000-0
消息体 由多个 field-value 对组成,类似 Hash task_id:1001, status:new
Stream Key Redis 键,存储整个消息流 order:stream
阻塞读取 支持 XREAD 阻塞等待新消息 XREAD BLOCK 5000
关键命令速览:
  • XADD key * field value [field value ...] — 追加消息
  • XRANGE key start end — 按 ID 范围查询
  • XREAD COUNT n BLOCK ms STREAMS key id — 阻塞读取
  • XLEN key — 获取消息长度

4.2 消费者组(Consumer Group)

消费者组是 Stream 实现消息负载均衡与可靠消费的核心机制。一个消费者组包含多个消费者,每条消息只会被组内的一个消费者消费,且消费状态由 Redis 服务端维护。

消费者组 vs 普通 XREAD:普通 XREAD 是广播模式,所有消费者都会收到相同消息;消费者组实现的是“点对点”模式,每条消息只投递给一个消费者。

4.2.1 消费者组核心概念

概念 说明 命令/属性
组名 消费者组的唯一标识 XGROUP CREATE
消费者 组内的消费实例,可动态加入/退出 自动注册
待处理列表(PEL) 已投递但未确认的消息列表 XPENDING
last_delivered_id 组内最后投递的消息 ID 服务端维护
消费者偏移量 每个消费者已读取的位置 独立维护

4.2.2 消费者组工作流程

  1. 创建组:XGROUP CREATE mystream mygroup $($ 表示从最新消息开始消费)
  2. 消费者读取:XREADGROUP GROUP mygroup consumer1 COUNT 1 BLOCK 2000 STREAMS mystream >
  3. 消息投递:服务端将消息分配给组内某个消费者,并记录到 PEL
  4. 消息确认:XACK mystream mygroup message-id,从 PEL 中移除
  5. 失败重试:通过 XPENDING 查看未确认消息,使用 XCLAIM 转移所有权
注意:消费者组使用 > 符号表示“只读取从未投递给任何消费者的新消息”。如果指定具体 ID,则用于读取历史消息或故障恢复。
消费者组消息投递与确认流程 Redis Stream 消费者组 (mygroup) Consumer A Consumer B Consumer C PEL (待处理列表) XACK 确认移除

4.3 消息确认机制(ACK)

消息确认是保证至少一次投递语义的关键。消费者处理完消息后,必须显式调用 XACK 通知 Redis 服务端,否则消息会一直留在 PEL 中,成为待处理消息。

4.3.1 未确认消息的处理

  • 查看待处理消息:XPENDING mystream mygroup 返回 PEL 中的消息数量、最小/最大 ID 及每个消费者的待处理数
  • 查看详情:XPENDING mystream mygroup - + 10 列出具体消息 ID、所属消费者、空闲时间、投递次数
  • 消息认领:XCLAIM mystream mygroup consumer2 60000 message-id 将超时未确认的消息转移给其他消费者
  • 自动认领:XAUTOCLAIM mystream mygroup consumer2 60000 0-0 自动扫描并认领超时消息
最佳实践:设置合理的 ACK 超时时间(如 60 秒),结合 XAUTOCLAIM 实现故障自动恢复。避免 PEL 无限增长导致内存泄漏。

4.3.2 消息投递语义对比

语义 实现方式 适用场景
最多一次 XREAD 读取后不确认 日志采集、可丢失监控
至少一次 XREADGROUP + XACK 订单处理、任务调度
精确一次 XREADGROUP + XACK + 业务幂等 支付回调、金融交易

4.4 任务去重方案:Stream + 幂等性

由于 Stream 保证至少一次投递,消费者可能收到重复消息。常见的去重方案包括:

  1. 业务 ID 去重:在消息体中携带全局唯一 ID(如 task_id),消费者处理前检查是否已处理
  2. Redis Set 去重:使用 SADD 记录已处理 ID,设置过期时间防止内存膨胀
  3. Stream 消息 ID 去重:利用消息 ID 的唯一性,结合 XACK 状态判断
推荐方案:使用 SETNX 或 SET EX NX 实现分布式锁去重,配合 Stream 的 PEL 机制,即使消费者崩溃重启也不会丢失消息。
任务去重与消息确认架构 生产者 (Producer) Redis Stream 消费者组 消费者 (Consumer) 去重模块 (Redis Set) XACK 确认

4.5 生产级配置建议

配置项 推荐值 说明
消息最大长度 MAXLEN ~ 100000 使用近似裁剪,防止内存无限增长
消费者组数量 1~3 个 每个业务场景一个组,避免组过多导致性能下降
ACK 超时时间 60~120 秒 根据业务处理耗时设定,超时后自动认领
PEL 大小监控 告警阈值 10000 PEL 过大说明消费者处理不及时或存在死信
消息体大小 < 1 KB 避免大消息阻塞 Stream 写入
避坑指南:
  • 不要使用 XGROUP CREATE 的 MKSTREAM 参数自动创建空 Stream,可能导致消费者组指向不存在的 ID
  • 消费者崩溃后,未 ACK 的消息会一直留在 PEL,需定期使用 XAUTOCLAIM 清理
  • Stream 的 MAXLEN 是近似裁剪,精确裁剪会阻塞写入,生产环境务必使用 ~ 近似模式