Redis订阅消息老是重复收到,重发问题到底咋解决啊?
- 问答
- 2025-12-29 00:23:04
- 3
这个问题确实是使用Redis订阅发布(Pub/Sub)功能时一个非常经典和头疼的情况,你不是一个人在为这个烦恼,核心原因在于Redis的Pub/Sub模型本身就是一个“发后即忘”的机制,它不保证消息持久化,也不提供任何形式的确认机制,这意味着,它天生就容易导致消息丢失或重复消费,下面我们掰开揉碎了说清楚为什么,以及怎么办。
我们必须明白为什么消息会“重复收到”。
根据网络资料和常见实践案例,重复收到消息通常不是Redis服务器本身把一条消息发送了两次(除非发布者确实发布了两次),而是源于你的订阅者客户端 出现了问题,主要有以下几种情况:
-
客户端网络不稳定或断开重连:这是最常见的原因,想象一下这个场景:你的订阅者(Subscriber)程序已经连接上了Redis,并且订阅了某个频道(Channel),这时,发布者(Publisher)发布了一条消息,Redis服务器顺利地将这条消息推给了你的订阅者,就在这个消息到达你的订阅者程序之后、你的程序还没来得及完全处理它之前,网络突然抖动了一下,或者你的程序因为某些原因(如GC停顿)导致与Redis的连接暂时断开了,Redis服务器检测到连接断开,它就会认为这个订阅者离线了,过了一会儿,你的程序重连成功,并自动重新执行了订阅(SUBSCRIBE)命令。关键在于:对于断开连接期间以及重连后新发布的消息,Redis会正常推送,但它绝不会为断开期间“已经推送过但可能未被客户端处理”的消息做任何保留或重发判断。 问题就在于,你的应用程序可能没有一种机制来区分“一条新消息”和“一条已经处理过但未确认的消息”,如果程序重启或重连后,没有记录下处理进度,或者从某个数据源(比如数据库)重新拉取任务,就极有可能导致同一条业务逻辑的消息被再次发布到Redis,然后被你再次收到。
-
发布者重复发布:这个原因相对直接,就是发布消息的代码逻辑可能有问题,一个按钮被用户连续点击了两次,触发了两次发布操作;或者一个任务因为超时等原因被调度系统重新执行,导致同一个任务生成了两条一模一样的消息,在这种情况下,Redis只是忠实地执行了两次发布命令,所以订阅者自然会收到两条相同的消息。
-
订阅者程序异常重启:和网络断开类似,如果你的订阅者程序在处理完消息后、还没来得及记录“我已处理完毕”的状态时就崩溃了,那么当程序重新启动后,它对于上一条消息的处理状态是未知的,如果系统设计是“从零开始消费”,那么它就会等待着下一条消息的到来,而这“下一条”消息可能就包含了之前已经处理过的业务数据。
到底该怎么解决这个“重发”问题呢?
很遗憾,答案不是去修改Redis的Pub/Sub本身,因为它生来如此,我们必须在应用程序层面,也就是在业务代码中构建“幂等性”防护。
“幂等性”这个词听起来专业,其实意思很简单:无论你收到同一条消息多少次,它对你系统产生的影响应该和只收到一次是完全一样的。 就像你按电梯的楼层按钮,按一次和按十次,电梯最终都只会到你按的那个楼层。
具体怎么做?这里有几个切实可行的思路:
消息ID与去重表(最常用且有效)
让发布者在发送每条消息时,都为其生成一个全局唯一的ID(比如UUID,或者由时间戳、机器ID、序列号组成的ID),这个ID需要包含在消息体内。
在订阅者这边,在处理消息之前,先拿着这个消息ID去一个“地方”查一下,看这个ID是否已经被处理过了,这个“地方”可以是一张数据库的表(比如MySQL的去重表),或者利用Redis自身的Set或String数据结构来存储已经处理过的ID(用 SET unique_message_id 1 命令,并设置一个合理的过期时间)。
处理流程变成:
- 收到消息 -> 解析出消息ID。
- 查询Redis或数据库:这个ID存在吗?
- 如果存在,说明是重复消息,直接丢弃(acknowledge,如果用的其他模式的话),不做任何业务处理。
- 如果不存在,则进行业务处理。处理成功后,立刻将这个ID写入“去重表”或Redis中,然后再确认消息。
利用更高级的消息队列 如果你发现你的业务场景对消息的可靠性要求很高,既不能丢也不能重复(或者说需要有能力方便地处理重复),那么或许应该考虑换一个消息中间件,而不是死磕Redis Pub/Sub。 Redis本身也提供了更可靠的队列模式,叫做 Stream(从Redis 5.0开始引入),Stream提供了类似Kafka或RabbitMQ的消费组(Consumer Group)概念、消息持久化、以及消息确认(ACK)机制,虽然在使用不当时仍然可能重复(比如ACK了但程序崩溃,导致消息重新分配给组内其他消费者),但它提供了更好的基础框架让你来实现幂等性。 像Apache Kafka、RabbitMQ这类专业的消息队列,在设计之初就更多地考虑了可靠投递和消费进度管理,它们通常是复杂业务场景下更稳妥的选择。
业务逻辑本身实现幂等 在某些情况下,你可以让业务操作本身具备幂等性。“将用户A的账户余额设置为100元”这个操作就是幂等的,执行一次和执行十次结果都一样,而“将用户A的账户余额增加100元”这个操作就不是幂等的,在设计业务接口时,尽量偏向使用幂等性的操作,可以从根本上减少重复消息带来的副作用。
Redis订阅消息老是重复收到,根源在于其“发后即忘”的简单模型,解决之道不在于Redis配置,而在于你的应用层设计,最直接有效的方法就是引入消息ID和应用层去重检查(幂等性设计),如果业务非常重要且复杂,评估并切换到Redis Stream或专业消息队列是更长远的选择,在分布式系统中,网络和故障是常态,“至少一次”或“最多一次”投递是常见语义,实现“精确一次”需要依靠消费者端的幂等性来最终保证。

本文由帖慧艳于2025-12-29发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://www.haoid.cn/wenda/70335.html
