[TOC] #### 1. 前言 --- 上一讲我们聊了消息不丢的问题,用 Publisher Confirm、Return、持久化、手动 ACK 四道防线,基本能保证消息不丢 但你有没有想过另一个问题:消息没有丢,但是重复了怎么办 ? 一个真实的事故: Producer 发了一条订单消息给 RabbitMQ,RabbitMQ 收到了,也写入队列了 但在返回确认的路上,网络抖动了,Producer 等了一会儿没收到确认,超时了 Producer 的逻辑是:超时就重发。于是又发了一条一模一样的订单消息,RabbitMQ 里现在有两条相同的消息 消费者收到第一条,扣了库存,消费者收到第二条,又扣了一次库存,用户买了一件商品,库存扣了两件 上一讲我们解决的是:消息不丢,这一讲要面对的是:消息不重复 可靠性和不重复是两回事,这是两个完全不同的问题: | | 不丢 | 不重复 | | ------------ | ------------ | ------------ | | 目标 | 每条消息都能被消费 | 同一条消息只消费一次 | | 难点 | 网络、磁盘、ACK | 重试、网络抖动、并发 | | RabbitMQ 能保证吗 | 基本能(配合配置) | 不能 | RabbitMQ 能帮你尽量不丢消息,但它 **无法天然保证业务不重复** #### 2. 为什么会出现重复消息 --- 重复消息的来源有很多: 一、Producer 重发 就像上面的例子,确认丢失导致 Producer 以为发送失败,重发一次 二、Consumer ACK 丢失 消费者处理完了,发了 ACK,但 ACK 在网络上丢了 RabbitMQ 没收到确认,以为消费者挂了,把消息重新投递给另一个消费者,同一条消息被两个消费者各处理了一次 三、消费者重启 消费者收到消息,处理完了,还没来得及发 ACK。 进程崩了,重启之后,RabbitMQ 把消息重新投递,消费者又处理了一次 四、重试机制 消费者处理失败,消息被 nack 重新入队,又被同一个消费者取到,再处理一次 #### 3. 幂等性 --- RabbitMQ 的边界在哪里 ? RabbitMQ 的职责是:消息传递,它保证的是:消息尽量不丢、尽量送达 至于「这条消息我之前处理过了,别再处理了」这是 “业务层” 的事情 RabbitMQ 不知道你的业务逻辑,不知道「扣库存」应该只扣一次,所以你需要在消费者端自己做去重 这个能力叫:幂等性 #### 4. 本文小结 --- | 问题 | 谁来解决 | | ------------ | ------------ | | 消息不丢 | RabbitMQ + 配置(Confirm、Return 、持久化、ACK) | | 消息不重复 | 业务层自己处理(幂等性) | 记住一句话:RabbitMQ 保证消息到达,不保证消息只到一次 下一讲,我们来聊消费幂等性:怎么保证同一条消息不管收到几次,结果都一样