主题切换
解决重复消费
一、重复消费的成因
重复消费的核心诱因有两类:
- 网络抖动:消费者处理完消息后,ack回执丢失,MQ未收到确认,消息被重新投递
- 消费者宕机:消费者处理消息过程中服务宕机,未发送ack,消息重回队列后被重新投递
该问题在Kafka、RabbitMQ、RocketMQ等主流消息中间件中均可能出现。
二、通用解决方案:幂等性设计
核心思路是让同一条消息执行多次,对业务数据的影响和执行一次完全相同,关键步骤:
- 为每条消息设置业务唯一标识ID(如支付ID、订单ID、文章ID)
- 消费前校验该标识ID是否已被处理:
- 已处理:直接跳过,不执行业务逻辑
- 未处理:执行业务逻辑,并记录该标识ID的处理状态
三、常见幂等实现方案
- 数据库锁方案:悲观锁、乐观锁
- 分布式锁方案:基于Redis、ZooKeeper实现分布式锁,保证同一时间只有一个线程处理该消息
- 状态校验方案:通过业务表的状态字段(如订单状态)判断消息是否已处理
四、面试高频问答:悲观锁与乐观锁的区别
核心思想不同
- 悲观锁:认为并发冲突大概率会发生,每次操作数据前都先加锁,其他线程阻塞等待
- 乐观锁:认为并发冲突大概率不会发生,操作数据时不加锁,仅在提交更新时校验是否冲突
实现方式不同
- 悲观锁:通过数据库的
SELECT ... FOR UPDATE语句实现,查询时直接锁定数据行 - 乐观锁:通过版本号(version字段)或时间戳实现,更新时判断版本号是否与读取时一致,一致则更新,不一致则重试
- 悲观锁:通过数据库的
性能表现不同
- 悲观锁:并发高时会导致大量线程阻塞,性能较差,适合并发冲突频繁的场景
- 乐观锁:无阻塞等待,性能更高,适合并发冲突少的场景
典型应用场景
- 悲观锁:支付扣减库存、订单锁定等强一致性业务
- 乐观锁:文章点赞、阅读量更新、接口防重提交等并发冲突低的场景
五、面试高频问答:重复消费如何解决?
答: 重复消费的核心解决方案是实现幂等性,通用步骤如下:
- 为每条消息设置业务唯一标识ID(如订单ID、支付ID);
- 消费前校验该标识ID是否已被处理;
- 处理方式可采用数据库锁(悲观锁/乐观锁)、分布式锁或状态校验方案,确保同一消息仅被处理一次。