系列导航:

一旦业务进入异步处理,很多团队都会经历一个相似阶段:先用 Redis 快速把消息“发起来”,再逐渐发现一些问题开始出现。

比如:

  • 为什么订阅者重启后,刚才那条消息就没了
  • 为什么同样是“队列”,有的消息能回看,有的完全追不到
  • 为什么消费者挂了之后,不知道哪些消息已经处理、哪些还没处理
  • 为什么补偿、重试、积压排查做着做着,越来越像在手写一个 MQ

这些问题的根源通常不是 Redis 不好用,而是Pub/Sub 和 Stream 解决的不是同一种异步问题。更重要的是,它们都不是专业 MQ 的完全替代品。

这一篇只讲三件事:

  • Pub/Sub 适合什么,不适合什么
  • StreamPub/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 做异步,不妨先问四个问题:

  1. 这条消息丢了,业务能接受吗?
  2. 消费者离线后,消息是否必须补回来?
  3. 我是否需要知道“已投递”和“已处理完成”之间的差异?
  4. 我是否已经开始自己补重试、死信、回放和治理工具?

如果前两个问题回答“必须保证”,第三个问题回答“必须看见”,第四个问题回答“已经在补”,那就基本说明:你需要的不是一个 Redis 技巧,而是一套更成熟的 MQ 能力。

反过来说,如果你的需求只是:

  • 应用内轻量广播
  • 小规模事件流
  • 可接受一定工程折中

那么 Redis Pub/SubStream 仍然可能是很高性价比的选择。

关键不在于它们能不能用,而在于你是否清楚它们到底在替你解决哪一类问题。

本篇解决了什么:

  • 区分了 Pub/Sub 的在线广播语义与 Stream 的事件流/消费组语义
  • 说明了 Stream 相比 Pub/Sub 在保留、确认、pending 和补处理上的边界能力
  • 给出了“广播、协作处理、成熟 MQ 能力”三类异步需求的判断方式
  • 明确了什么时候不该继续把 Redis 往专业消息队列的角色上推

下一篇:Redis 内存治理:TTL、淘汰策略、碎片与大 Key(七)