4. Redis Streams 详解:Stream 数据结构、消费者组、消息持久化与 PUB/SUB 对比
Redis Streams 是 Redis 5.0 引入的核心数据结构,专为消息队列与事件流场景设计。它弥补了 PUB/SUB 无持久化、无回溯消费的短板,同时提供了比 List 更丰富的消费者组模型。本节从数据结构、消费者组、持久化机制三个维度深入剖析,并与 PUB/SUB 做系统性对比。
4.1 Stream 数据结构核心模型
Stream 本质上是一个仅追加的日志结构,每条消息由唯一的 ID 和 键值对列表组成。ID 默认基于毫秒时间戳和序列号生成,保证全局有序且唯一。
核心特性:
- 消息 ID:格式
<millisecondsTime>-<sequenceNumber>,支持精确时间戳排序与范围查询。 - 消息体:由多个 field-value 对组成,类似 Hash 结构,支持复杂结构化数据。
- 阻塞读取:支持
XREAD和XREADGROUP的阻塞模式,实现实时消费。 - 范围查询:通过
XRANGE按 ID 范围回溯历史消息。
# 添加消息到 Stream
XADD mystream * sensor-id 1234 temperature 19.8
# 输出: "1712345678000-0"
# 读取最新消息(阻塞模式)
XREAD COUNT 1 BLOCK 5000 STREAMS mystream 0
# 按时间范围查询
XRANGE mystream 1712345678000 1712345679000
4.2 消费者组(Consumer Group)机制
消费者组是 Stream 最强大的特性,它允许多个消费者协同消费同一个消息流,同时保证消息不丢失、负载均衡和故障转移。
| 概念 | 说明 | 关键命令 |
|---|---|---|
| 消费者组 | 一组消费者的逻辑集合,共享消息消费进度 | XGROUP CREATE |
| 消费者 | 组内的具体消费实例,拥有独立名称 | XREADGROUP |
| 待处理列表(PEL) | 已投递但未确认的消息列表,保证至少一次投递 | XPENDING |
| 消息确认 | 消费者处理完成后显式确认,从 PEL 移除 | XACK |
| 重新投递 | 超时未确认的消息可被其他消费者重新领取 | XCLAIM |
# 创建消费者组
XGROUP CREATE mystream mygroup $
# 消费者 A 读取消息(阻塞模式)
XREADGROUP GROUP mygroup consumerA COUNT 1 BLOCK 5000 STREAMS mystream >
# 消费者 B 读取消息
XREADGROUP GROUP mygroup consumerB COUNT 1 BLOCK 5000 STREAMS mystream >
# 确认消息
XACK mystream mygroup 1712345678000-0
# 查看待处理消息
XPENDING mystream mygroup
最佳实践:消费者组中的
> 符号表示只读取从未投递给任何消费者的新消息。使用 XCLAIM 可以处理消费者崩溃导致的未确认消息,实现故障转移。
4.3 消息持久化与可靠性保障
Stream 的消息持久化依赖 Redis 的 RDB 和 AOF 持久化机制。与 PUB/SUB 不同,Stream 的消息写入后即持久化到磁盘,重启后可恢复。
| 持久化方式 | 对 Stream 的影响 | 推荐配置 |
|---|---|---|
| RDB(快照) | 定期保存 Stream 数据到磁盘,重启时加载 | save 900 1(至少 1 次写入) |
| AOF(追加文件) | 记录每条 XADD 命令,支持秒级数据恢复 | appendfsync everysec |
| 混合持久化 | RDB + AOF 结合,兼顾恢复速度与数据安全 | aof-use-rdb-preamble yes |
注意:即使启用了持久化,Stream 的消息在消费者组中仍可能因消费者崩溃而丢失(未确认消息)。必须配合
XCLAIM 和 XPENDING 监控实现完整的可靠性保障。
4.4 Streams 与 PUB/SUB 的全面对比
PUB/SUB 是经典的发布-订阅模式,而 Streams 是消息队列与事件流的融合体。两者在消息模型、持久化、消费方式上存在本质差异。
| 对比维度 | Redis Streams | Redis PUB/SUB |
|---|---|---|
| 消息持久化 | 支持(依赖 RDB/AOF) | 不支持(消息即发即失) |
| 消息回溯 | 支持(通过 XRANGE 按 ID 范围查询) | 不支持(无法消费历史消息) |
| 消费者组 | 支持(负载均衡、故障转移) | 不支持(所有订阅者收到相同消息) |
| 消息确认 | 支持(XACK 机制) | 不支持(无确认概念) |
| 消息模型 | 点对点 + 发布订阅 | 仅发布订阅 |
| 性能 | 中等(受持久化和 PEL 影响) | 极高(纯内存转发) |
| 适用场景 | 任务队列、事件溯源、日志收集 | 实时通知、广播、轻量级消息 |
// PUB/SUB 示例(无持久化)
SUBSCRIBE channel:alerts
// 发布者
PUBLISH channel:alerts "Server down!"
// Stream 示例(有持久化 + 消费者组)
XADD stream:alerts * event "server_down" host "web-01"
XREADGROUP GROUP alert-group worker1 STREAMS stream:alerts >
选型建议:
- 需要消息不丢失、回溯消费、负载均衡 → 选择 Streams。
- 追求极致性能、广播通知、临时事件 → 选择 PUB/SUB。
- 混合场景:Streams 做核心业务队列,PUB/SUB 做实时推送通知。
4.5 实践案例:使用 Streams 构建可靠任务队列
以下示例展示如何使用 Streams 实现一个生产者-消费者任务队列,包含失败重试和死信处理。
// 生产者:添加任务
XADD task:queue * type "email" to "user@example.com" subject "Welcome"
// 消费者组:创建组
XGROUP CREATE task:queue workers $
// 消费者:循环处理任务
while (true) {
// 阻塞读取任务
result = XREADGROUP GROUP workers worker1 COUNT 1 BLOCK 5000 STREAMS task:queue >
if (result) {
message = result[0][1][0]
id = message[0]
data = message[1]
// 处理任务
success = processTask(data)
if (success) {
// 确认消息
XACK task:queue workers id
} else {
// 检查重试次数
pending = XPENDING task:queue workers - + 10
if (pending[id].deliveryCount > 3) {
// 移入死信队列
XADD task:queue:dead * original_id id error "max_retries"
XACK task:queue workers id
}
// 否则等待 XCLAIM 重新投递
}
}
}
生产环境建议:
- 使用
XTRIM限制 Stream 长度,避免内存无限增长。 - 监控
XPENDING队列长度,及时发现消费积压。 - 为每个消费者设置超时时间,配合
XCLAIM实现自动故障转移。 - 使用死信队列处理多次失败的消息,避免无限重试。