用Redis订阅模式做消息发布系统,顺带聊聊补偿机制那些事儿
- 问答
- 2026-01-12 06:54:45
- 2
说到用Redis做消息发布系统,很多人第一个想到的就是它的订阅发布功能,也就是PUB/SUB,这个功能用起来特别简单直接,就像我们现实生活中听广播一样。
想象一下,Redis服务器就是一个大喇叭(广播站),有一些应用程序,我们叫它们“订阅者”(Subscriber),它们对某些特定的话题(Channel)感兴趣,一个程序专门关心“订单创建”这个话题,另一个程序关心“用户注册”,这些订阅者会跑到Redis那里,说:“喂,Redis,我要订阅‘订单创建’这个话题,有消息了你马上告诉我。”这个过程就叫“订阅”(SUBSCRIBE)。
当另一个应用程序,我们叫它“发布者”(Publisher),创建了一个新订单时,它就会对着Redis这个大喇叭喊:“大家好,这里是‘订单创建’频道,现在发布一条新消息,消息内容是订单ID是12345。”这个过程就叫“发布”(PUBLISH)。
Redis一收到这条发布命令,立刻就会把“订单ID是12345”这条消息,原封不动地送到所有之前订阅了“订单创建”这个频道的订阅者手里,这些订阅者程序收到消息后,就可以执行自己的逻辑了,比如减库存、发短信通知等等。
这一切看起来非常完美,实时性又高,代码写起来也简单,这里就有一个关键问题,也是Redis PUB/SUB一个众所周知的“坑”:它不保证消息的可靠性。
什么意思呢?还拿广播站举例子,如果你在听广播的时候,突然停电了,或者你走神了没听清,广播站是不会为你重播的,错过了就是错过了,Redis的PUB/SUB也是同样的道理,它是一种“fire-and-forget”(发射后不管)的模式,它只负责把消息发给当前正在连接的订阅者,如果一个订阅者因为网络波动、程序重启等各种原因,在发布消息的那一瞬间没有连着Redis,那么这条消息它就永远收不到了,Redis内部不会为任何订阅者保存这些消息。
正因为有这个天生的缺陷,如果我们做的系统要求消息不能丢,比如支付成功通知这种关键业务,直接使用PUB/SUB就得非常小心了,这时候,我们就得聊聊“补偿机制”那些事儿了,说白了,补偿机制就是一套备用的方案,用来处理当主流程出问题时,怎么把事儿给补上,保证最终结果是对的。
针对Redis PUB/SUB可能丢消息的问题,常见的补偿思路有几种,都不是直接用PUB/SUB,而是用Redis的其他数据结构来模拟更可靠的消息队列。
一种常见的做法是借用Redis的列表(List)或者更专业的Stream数据结构,以List为例,这就像是一个待办事项清单,发布者不再是用PUBLISH命令去“喊一嗓子”,而是用LPUSH命令把消息(比如订单ID)塞进一个列表的头部,而订阅者这边呢,它不再是被动地等Redis推送,而是主动地、不停地用RPOP命令从这个列表的尾部取出消息来处理,这样即使订阅者程序宕机了一会儿,重启之后,它还可以继续去列表里取它宕机期间积压的消息,这就实现了消息的持久化,解决了“掉线就丢消息”的问题。
但这样又带来一个新问题:如果订阅者从列表里取出了消息,正在处理的时候突然崩溃了,这条消息不就也没处理完,而且因为已经从列表里取出来了,别的订阅者也拿不到,不就丢了吗?为了解决这个问题,又引入了更复杂的机制,比如Redis的Stream数据结构,它支持“消费者组”(Consumer Group)的概念,订阅者从Stream里取出一条消息后,这条消息并不会立刻删除,而是被标记为“正在处理”状态,只有当订阅者明确告诉Redis“这条消息我处理完了”,消息才会被移除,如果订阅者处理到一半崩溃了,过一段时间,这条消息又会重新被其他空闲的订阅者拿到并处理,这就构成了一个更健壮的补偿机制。
还有一种补偿思路是放在业务层面,对于订单状态这种关键信息,负责发消息的系统可以不那么信任消息队列,而是定期地主动去扫描数据库,看看有没有状态是“已创建”但还没通知到下游服务的订单,然后重新发一次通知,这叫“对账”或者“兜底查询”,虽然实时性差一些,但能保证最终数据一致。
总结一下(这里不是结尾,是内容的一部分):Redis自带的PUB/SUB适合用在那些丢了消息也无所谓的场景,比如实时推送在线人数、聊天消息(偶尔丢一两条可能能接受),但如果业务要求很高,不能丢消息,那么直接用它风险很大,我们通常会用Redis的List或者Stream来模拟一个更可靠的消息队列,并配合各种确认和重试机制来作为补偿,如果要求再高,可能就得考虑用专业的消息中间件,比如RabbitMQ、Kafka了,它们天生就是为高可靠的消息传递而设计的,选择哪种方案,归根结底是要在开发的简便性、性能和可靠性之间做权衡。

本文由钊智敏于2026-01-12发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/79165.html
