3. Redis Stream 核心数据结构详解
Redis Stream 是 Redis 5.0 引入的专为消息队列与事件流场景设计的数据结构。它并非简单的列表或发布/订阅,而是融合了持久化、消费者组、消息确认与重试机制的完整消息中间件模型。理解 Stream 的四大核心组件——Stream、Consumer Group、Consumer 与 Pending Entries List——是掌握异步任务接口契约的基础。
3.1 Stream:消息的持久化日志
Stream 本质上是一个仅追加的日志结构。每条消息都有一个唯一的 64 位整数 ID(通常为 <毫秒时间戳>-<序列号>),并包含一个或多个键值对(field-value)。
- 消息 ID:自动生成或由客户端指定,保证全局有序且唯一。
- 消息内容:以哈希表形式存储,支持多个字段,适合结构化数据。
- 持久化:Stream 数据可持久化到 RDB/AOF,重启后不丢失。
- 范围查询:支持按 ID 范围、时间范围正向或反向遍历。
关键特性:Stream 的日志结构天然支持“回溯消费”——新消费者可以从任意历史位置开始读取,不会丢失已过期的消息(除非被显式裁剪)。
3.2 Consumer Group:多消费者协作模型
Consumer Group 是 Stream 实现负载均衡与容错的核心机制。一个 Stream 可以挂载多个消费者组,每个组独立维护自己的消费进度。
| 概念 | 说明 |
|---|---|
| 组内竞争 | 一条消息只会被组内的一个消费者消费(Round-Robin 分配)。 |
| 独立进度 | 每个组维护一个 last_delivered_id,记录组内已分配的最新消息 ID。 |
| 消息不删除 | 消息被消费后仍保留在 Stream 中,直到被 XDEL 或 XTRIM 清理。 |
| 组间隔离 | 不同组的消费者可以独立消费同一条消息,互不干扰。 |
设计意图:Consumer Group 解决了“广播”与“点对点”的矛盾——同一组内是点对点,不同组之间是广播。
3.3 Consumer:组内的消费单元
Consumer 是实际处理消息的客户端实例。每个 Consumer 必须属于一个 Consumer Group,并在组内拥有唯一名称。
- 自动注册:当消费者首次使用
XREADGROUP读取消息时,Redis 自动将其注册到组中。 - 状态维护:每个 Consumer 维护自己的 Pending Entries List,记录已接收但未确认的消息。
- 空闲检测:可通过
XINFO CONSUMERS查看消费者的空闲时间、待处理消息数等指标。
3.4 Pending Entries List (PEL):消息确认与重试机制
PEL 是每个 Consumer 私有的待确认消息列表,是 Stream 实现至少一次投递语义的基石。
# 消费者读取消息后,消息 ID 被加入 PEL
> XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) "1700000000000-0"
2) 1) "task_id"
2) "1001"
3) "payload"
4) "{\"type\":\"email\"}"
# 消费者处理完成后,必须显式确认
> XACK mystream mygroup 1700000000000-0
(integer) 1 # 消息从 PEL 中移除
| PEL 操作 | 命令 | 效果 |
|---|---|---|
| 添加 | XREADGROUP |
消息投递给消费者后自动加入 PEL |
| 确认 | XACK |
从 PEL 中移除该消息 ID |
| 查询 | XPENDING |
查看 PEL 中的消息总数、最老消息、各消费者待处理数 |
| 认领 | XCLAIM |
将其他消费者 PEL 中的消息转移给当前消费者(用于故障转移) |
重要:如果消费者崩溃且未调用
XACK,消息将永久停留在 PEL 中。必须通过 XCLAIM 或 XAUTOCLAIM 将超时消息重新分配给其他消费者,否则会导致“消息泄漏”。
3.5 核心组件关系图
下图展示了 Stream、Consumer Group、Consumer 与 PEL 之间的完整交互流程:
3.6 消息生命周期与状态流转
一条消息从写入到最终确认,经历以下关键状态:
- 已写入:消息追加到 Stream 日志,尚未被任何消费者组关注。
- 已投递:Consumer Group 将消息分配给某个 Consumer,消息 ID 进入该 Consumer 的 PEL。
- 待确认:消息在 PEL 中等待
XACK。若超时未确认,可被其他 Consumer 通过XCLAIM认领。 - 已确认:Consumer 调用
XACK,消息从 PEL 中移除,但仍在 Stream 中。 - 已删除:通过
XDEL或XTRIM从 Stream 中物理移除。
异步任务场景:在异步任务队列中,PEL 扮演“任务追踪器”角色。任务投递后进入 PEL,Worker 处理完成后发送 XACK。如果 Worker 宕机,任务在 PEL 中滞留,监控系统通过
XPENDING 发现并触发 XCLAIM 重新调度,确保任务最终被执行。
3.7 关键命令速查表
| 组件 | 命令 | 功能 |
|---|---|---|
| Stream | XADD |
追加消息到 Stream |
| Stream | XRANGE |
按 ID 范围查询消息 |
| Stream | XTRIM |
裁剪 Stream 长度 |
| Group | XGROUP CREATE |
创建消费者组 |
| Group | XREADGROUP |
从组中读取消息 |
| Consumer | XINFO CONSUMERS |
查看消费者状态 |
| PEL | XACK |
确认消息处理完成 |
| PEL | XPENDING |
查看待处理消息 |
| PEL | XCLAIM |
转移待处理消息所有权 |
3.8 小结
Redis Stream 通过日志结构保证消息持久化与有序性,通过Consumer Group实现水平扩展与负载均衡,通过PEL提供可靠的消息确认与故障恢复能力。这四个组件共同构成了一个轻量级但功能完备的异步消息中间件,是构建分布式异步任务系统的理想选择。