Redis 消息能力:Pub/Sub、Stream 与队列边界(六)
系列导航:
- 上一篇:Redis 数据结构实战:计数器、排行榜、去重与会话(五)
- 当前篇:Redis 消息能力:Pub/Sub、Stream 与队列边界(六)(本文)
- 下一篇:Redis 内存治理:TTL、淘汰策略、碎片与大 Key(七)
一旦业务进入异步处理,很多团队都会经历一个相似阶段:先用 Redis 快速把消息“发起来”,再逐渐发现一些问题开始出现。
比如:
- 为什么订阅者重启后,刚才那条消息就没了
- 为什么同样是“队列”,有的消息能回看,有的完全追不到
- 为什么消费者挂了之后,不知道哪些消息已经处理、哪些还没处理
- 为什么补偿、重试、积压排查做着做着,越来越像在手写一个 MQ
这些问题的根源通常不是 Redis 不好用,而是Pub/Sub 和 Stream 解决的不是同一种异步问题。更重要的是,它们都不是专业 MQ 的完全替代品。
这一篇只讲三件事:
Pub/Sub适合什么,不适合什么Stream比Pub/Sub多了什么边界能力,又多了哪些运维复杂度- 什么时候应该及时停手,不再继续把 Redis 往专业 MQ 的角色上推
Pub/Sub:它解决的是“在线广播”,不是“可靠投递”
Redis Pub/Sub 的心智模型非常简单:
- 发布者往一个 channel 发消息
- 当前在线的订阅者会收到消息
- 没有订阅者在线,就没有人接住这条消息
一个最简单的示意是:
PUBLISH order-events '{"event":"order.created","orderId":"1001"}'
SUBSCRIBE order-events
Pub/Sub 最适合什么场景
Pub/Sub 最适合的是即时广播,例如:
- 站内实时通知
- 配置变更广播
- WebSocket 网关之间的轻量事件同步
- 对“漏一条也能接受”的在线提示
这些场景有一个共同点:消息的价值主要在“现在立刻通知出去”,而不是“必须被可靠保存并最终处理完”。
Pub/Sub 最核心的边界
只要使用 Pub/Sub,就应该默认接受这些事实:
- 它不保存历史消息
- 订阅者离线期间的消息不会补发
- 很难知道某条消息到底有没有被业务真正处理完成
- 没有消费组、pending 列表、确认语义这些运行信息
也就是说,Pub/Sub 很适合“广播出去就算完成”的场景,不适合“必须可追踪、可补偿、可回放”的场景。
很多误用都来自一句模糊的话:“我们就是简单发个消息。”问题在于,你口中的简单,是只通知一下,还是必须最终被某个下游处理掉。
Stream:它解决的是“可保留、可确认、可协作的事件流”
如果说 Pub/Sub 关心的是“现在谁在线”,那么 Stream 关心的是“这条记录被写进流之后,后面怎么被多个消费者有组织地处理”。
一个最基础的使用姿势通常像这样:
XADD orders * event order.created orderId 1001
XGROUP CREATE orders order-group 0 MKSTREAM
XREADGROUP GROUP order-group c1 COUNT 10 STREAMS orders >
XACK orders order-group 1718278123456-0
Stream 比 Pub/Sub 多了什么
Stream 最重要的能力不是“语法更复杂”,而是它把消息流变成了一份可以保留、追踪和确认的数据结构。
这意味着你可以拥有:
- 流中的消息 ID
- 历史消息保留与按需裁剪
- 消费组,让多消费者协作而不是都收到同一条
- pending 记录,知道哪些消息已投递但还未确认
- 消费者故障后的重新认领与补处理
一旦你的业务开始问这些问题,Stream 通常就比 Pub/Sub 更合适:
- 某条消息到底有没有被处理完
- 某个消费者挂了,未确认的消息怎么办
- 积压在哪里,pending 多不多
- 能不能回看最近一段事件流做补偿
Stream 不是“更高级的 Pub/Sub”
很多人学到 Stream 后,容易产生一个误解:它只是“带持久化的 Pub/Sub”。这并不准确。
更准确的说法是:
Pub/Sub是广播模型Stream是事件流与消费组模型
两者的消费语义本来就不同。
例如,一个订单创建事件:
- 如果你的目标是让多个在线节点立即收到刷新提示,
Pub/Sub很合适 - 如果你的目标是让订单履约服务、通知服务、积分服务各自稳定处理自己的那份工作,
Stream更接近需求
所以不要把它们理解成“新旧替换关系”,而应该理解成“异步问题类型不同”。
判断边界时,先看你要的是广播、队列,还是事件流
很多异步设计选错,不是因为不了解命令,而是因为一开始没先分清问题类型。
当你要的是广播
如果业务目标是:
- 谁在线谁收到
- 重点是低延迟通知
- 不强调历史追溯
- 丢一条不会造成严重业务后果
那就优先考虑 Pub/Sub。
典型例子是:
- 运营后台改了配置,通知网关节点刷新本地缓存
- 在线用户收到实时提示
- 应用内做轻量事件通知
当你要的是可协作处理
如果业务目标变成:
- 一条消息必须被某个消费者组成员处理
- 需要确认处理完成
- 需要排查积压和重投递
- 需要保留一段时间的事件流做补偿
那就优先考虑 Stream。
典型例子是:
- 订单创建后驱动多个后续异步流程
- 导出任务、履约任务、异步审核任务由多个 worker 协作消费
- 需要知道“消息到了哪一步”的应用内事件流
当你其实要的是成熟 MQ 能力
如果你真正要的是下面这些能力,就不该继续假设 Redis 足够:
- 明确的死信队列体系
- 更成熟的延迟、重试、重投与投递策略
- 更复杂的路由拓扑或大规模消费者治理
- 跨系统、跨团队、长周期演进的消息基础设施
- 更稳定的积压治理、可观测性和运维工具链
此时与其继续在 Redis 周围补脚手架,不如直接使用专业 MQ。
什么时候不该再让 Redis 继续扮演 MQ
真正的分界线,通常不是“Redis 能不能凑合做”,而是“你为了凑合它,已经开始补多少消息系统能力”。
下面这些信号一旦频繁出现,就说明你已经接近边界:
你开始自己设计重试、死信和回放体系
如果业务已经要求:
- 多次重试后转入失败队列
- 人工或自动回放某批历史消息
- 按失败原因分类处理
那你其实已经在搭消息基础设施,而不只是“用一下 Redis”。
你开始高度依赖消费运行态排障
比如你经常需要知道:
- 哪个消费组积压最多
- 哪个消费者长时间未确认
- 某批消息是否跨实例重试过
这说明消息系统已经成为一个需要长期运维的核心部件。此时使用专业 MQ 往往比持续给 Redis 外挂治理脚本更稳。
你开始把业务解耦完全建立在消息系统上
当消息链路承载的是:
- 核心订单流转
- 跨多个服务域的异步协调
- 大规模事件分发
那系统对消息平台的可靠性、治理能力和团队协作边界要求都会显著提高。Redis 可以在局部场景很好用,但不一定适合作为整个组织级异步基础设施的唯一答案。
一个更实用的判断方式
判断要不要继续用 Redis 做异步,不妨先问四个问题:
- 这条消息丢了,业务能接受吗?
- 消费者离线后,消息是否必须补回来?
- 我是否需要知道“已投递”和“已处理完成”之间的差异?
- 我是否已经开始自己补重试、死信、回放和治理工具?
如果前两个问题回答“必须保证”,第三个问题回答“必须看见”,第四个问题回答“已经在补”,那就基本说明:你需要的不是一个 Redis 技巧,而是一套更成熟的 MQ 能力。
反过来说,如果你的需求只是:
- 应用内轻量广播
- 小规模事件流
- 可接受一定工程折中
那么 Redis Pub/Sub 或 Stream 仍然可能是很高性价比的选择。
关键不在于它们能不能用,而在于你是否清楚它们到底在替你解决哪一类问题。
本篇解决了什么:
- 区分了
Pub/Sub的在线广播语义与Stream的事件流/消费组语义 - 说明了
Stream相比Pub/Sub在保留、确认、pending 和补处理上的边界能力 - 给出了“广播、协作处理、成熟 MQ 能力”三类异步需求的判断方式
- 明确了什么时候不该继续把 Redis 往专业消息队列的角色上推