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
Stream 数据结构内部布局 消息 ID 1712345678000-0 sensor-id: 1234 temperature: 19.8 消息 ID 1712345678001-0 sensor-id: 1235 temperature: 20.1 消息 ID 1712345678002-0 sensor-id: 1236 temperature: 19.5 关键设计要点 • 消息 ID 由毫秒时间戳 + 序列号组成,保证全局有序且唯一。 • 每条消息可包含多个 field-value 对,支持结构化数据存储。 • Stream 内部使用基数树(Rax)索引,支持 O(log N) 的 ID 范围查询。 • 消息默认永久保留,可通过 XTRIM 或 MAXLEN 限制长度。

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 可以处理消费者崩溃导致的未确认消息,实现故障转移。
消费者组消息流转架构 Redis Stream 消息队列 消费者组 XREADGROUP 消费者 A 处理消息 1, 4, 7... 消费者 B 处理消息 2, 5, 8... 消费者 C 处理消息 3, 6, 9... PEL(待处理列表) 每个消费者维护独立的 PEL,记录已投递但未确认的消息。XACK 后移除,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 实现自动故障转移。
  • 使用死信队列处理多次失败的消息,避免无限重试。