一、重复消费的三种场景

1.发送消息时重复

当一条消息已被成功发送到服务端并完成持久化,此时出现了网络闪断或者客户端宕机,导致服务端对客户端应答失败。如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会收到两条内容相同并且Message ID也相同的消息

2.投递时消息重复

①消息消费的场景下,消息已投递到消费者并完成业务处理,当客户端给服务端反馈应答的时候网络闪断。为了保证消息至少被消费一次,消息队列RocketMQ版的服务端将在网络恢复后再次尝试投递之前已被处理过的消息,消费者后续会收到两条内容相同并且Message ID也相同的消息

②举例

举例来说,假设有一个名为 "order" 的主题(Topic),其中包含订单消息。有两个消费者 A 和 B 订阅了这个主题,并且设置为同一消费组(Consumer Group)。当新的订单消息到达时,RocketMQ 会将消息平均分配到两个消费者的消息队列中,让它们分别处理。但是如果消费者 A 处理消息的速度较慢,而消费者 B 处理消息的速度较快,这时就可能出现以下情况:

  • 消息被消费者 B 先消费。
  • 消息被消费者 A 后消费。

如果消费者 B 处理消息后出现了某种错误导致消息未被正确处理或者消费者 B 重启了消费端程序,此时 RocketMQ 会将未被确认消费的消息重新投递给消费组内的其他消费者,这时消费者 A 可能会重新消费之前已经被消费过的消息,导致消息的重复消费。

3.负载均衡时消息重复

①(包括但不限于网络抖动、Broker重启以及消费者应用重启)当消息队列RocketMQ版的Broker或客户端重启、扩容或缩容时,会触发Rebalance,此时消费者可能会收到重复消息。

②举例

  • 1.假设一个主题中包含订单消息,有两个消费者订阅了该主题并且属于同一个消费组。
  • 2.假设有4个队列,刚开始只有一个消费者C1,C1对应4个队列,此时消息已经投递给C1,但C1正在处理,这时候C2进来扩容,需要重新负载均衡,此时C2会抢夺C1的两条队列。
  • 3.在负载均衡过程中,RocketMQ 可能会将某个消息队列重新分配给消费者C2,如果此时消息队列中仍有未被确认消费的消息,那么该消息就会被消费者 C2 重复消费,从而导致消息重复。

二、幂等性(当消息重复时,结果是相同的)

1.举例

  • 1.Get操作是幂等的
  • 2.Post操作通常会添加新的资源,因此是不幂等的
  • 3.Put操作如果是set name = nameA是幂等的,
  • 如果是set balance = balnace + 100是非幂等的
  • 4.Delete根据业务确定,比如如果是删除不存在的数据则是相同的;删除存在的数据,第一次是删除掉了,后面的多次是并没有删除成功的

三、解决方案

1.利用数据库的唯一约束(如账单ID)实现幂等

2.乐观锁

①改造“将某账户余额增加100元”的业务逻辑。

为“将某账户余额增加100元”添加前置条件,变为:“如果某账户余额版本为2,则将账户X的余额增加100元”。

每次更新时,若数据中的版本号和消息中的版本号一直,则更新数据并且版本号+1,否则拒绝更新,从而实现了操作的幂等性。

③使用redis/布隆过滤器实现


原来是小袁呐
1 声望0 粉丝

引用和评论

0 条评论