面试题答案
一键面试可能出现的场景分析
- 网络波动:在消息被成功处理并已更新数据库后,向RabbitMQ发送确认消息时网络出现波动,导致RabbitMQ未收到确认,认为消息未被成功消费,从而再次投递该消息。当此重复消息再次被处理时,就会造成数据不一致。例如,订单系统中,订单已创建成功并在数据库记录,但确认消息丢失,重复消息再次创建订单,导致订单重复。
- 消费者故障恢复:消费者在处理消息过程中突然崩溃,此时消息可能已经部分处理,数据库状态已改变,但未完成全部处理流程也未发送确认。消费者重启后,RabbitMQ会重新投递该消息,若处理逻辑未做幂等处理,会导致数据重复更新。比如在库存系统中,库存已经减少,但消费者崩溃,重启后重复消息又减少一次库存。
- 消息队列重试机制:RabbitMQ的重试机制可能导致消息多次投递。如果消费者处理消息时抛出异常,RabbitMQ会按照重试策略重新投递消息。若异常原因是暂时的,且消费者未对重复消息进行处理,就会出现数据不一致。例如数据库连接短暂中断,导致消息处理失败,重试时可能重复更新数据。
解决方案
- 使用数据库唯一约束
- 原理:在数据库表中针对关键业务字段设置唯一约束。当重复消息进行插入操作时,数据库会因为唯一约束违反抛出异常,应用层捕获异常并忽略,从而保证数据的一致性。例如在订单表中,针对订单编号设置唯一索引,重复的订单插入操作会因违反唯一约束而失败。
- 优点:实现简单,借助数据库自身功能保证数据唯一性。
- 缺点:只能用于插入操作,对于更新操作无效;同时异常处理在应用层,增加应用复杂度。
- 消息幂等处理
- 在消息体中添加唯一标识:在生产者发送消息时,为每个消息生成一个唯一的标识(如UUID)。消费者在处理消息前,先检查数据库中是否已经处理过具有相同标识的消息。可以在数据库中创建一张专门的消息处理记录表,记录已处理消息的标识。若已处理则跳过,未处理则处理并记录。例如在日志系统中,为每条日志消息添加唯一ID,处理前先查询记录表。
- 优点:对各种操作(插入、更新等)都能有效处理重复消息,灵活性高。
- 缺点:增加了数据库查询操作和额外的表维护,可能影响性能。
- 使用分布式锁
- 原理:在消费者处理消息前,先获取分布式锁(如使用Redis实现的分布式锁)。只有获取到锁的消费者才能处理消息,处理完成后释放锁。当重复消息到达时,由于锁已被占用,其他消费者无法获取锁,从而避免重复处理。例如在分布式订单处理系统中,以订单ID作为锁的标识,处理订单消息前先获取锁。
- 优点:能有效避免同一时间重复处理消息,保证数据一致性。
- 缺点:引入了分布式锁,增加了系统复杂度和维护成本,同时可能存在锁超时等问题。