库存消费者处理消息M100成功并提交数据库,但在向消息队列确认前进程崩溃。队列随后重新投递M100。要避免库存再次扣减,较合理的设计是()。
至少一次投递允许同一消息再次到达。消费者需要识别已处理消息,并让去重记录与业务状态一起提交,否则在二者之间崩溃仍可能重复执行或漏执行。
选项分析
错误。确认丢失、锁超时和消费者重启都可能造成重投。
错误。先确认后处理会在确认成功、业务失败时造成消息丢失。
正确。幂等键与业务更新形成同一一致性边界,可以安全吸收重复投递。
错误。重复投递不代表业务应重复发生,库存会被错误扣减。
本题为什么容易错
“队列支持一次且仅一次”常被当成万能保证。即使传输层减少重复,消费者跨数据库、外部接口产生的业务副作用仍需自己的幂等边界。
简短答案
消息处理成功但确认丢失,怎样避免重复扣减库存,正确答案是 C(用消息ID或业务幂等键记录处理结果,并与库存更新放在同一原子事务或等效一致性边界中)。至少一次投递允许同一消息再次到达。消费者需要识别已处理消息,并让去重记录与业务状态一起提交,否则在二者之间崩溃仍可能重复执行或漏执行。
易混淆概念对比表
| 概念 | 本题判断 | 区别要点 | 记忆提示 |
|---|---|---|---|
| 相信队列绝不会重投,不保存任何处理标识 | 本题干扰项 | 错误。确认丢失、锁超时和消费者重启都可能造成重投。 | 看到该词不要急着选,先判断是否真正解决题干问题 |
| 收到消息后先确认,再决定是否处理业务 | 本题干扰项 | 错误。先确认后处理会在确认成功、业务失败时造成消息丢失。 | 看到该词不要急着选,先判断是否真正解决题干问题 |
| 用消息ID或业务幂等键记录处理结果,并与库存更新放在同一原子事务或等效一致性边界中 | 本题正确答案 | 正确。幂等键与业务更新形成同一一致性边界,可以安全吸收重复投递。 | 看到题干核心场景时优先联想到它 |
| 每次重投都额外再扣一次库存,以保持次数一致 | 本题干扰项 | 错误。重复投递不代表业务应重复发生,库存会被错误扣减。 | 看到该词不要急着选,先判断是否真正解决题干问题 |
本题易混淆选项怎么区分
- 相信队列绝不会重投,不保存任何处理标识:错误。确认丢失、锁超时和消费者重启都可能造成重投。
- 收到消息后先确认,再决定是否处理业务:错误。先确认后处理会在确认成功、业务失败时造成消息丢失。
- 每次重投都额外再扣一次库存,以保持次数一致:错误。重复投递不代表业务应重复发生,库存会被错误扣减。
知识点详解
至少一次投递通过重试降低消息丢失风险,代价是消费者可能多次收到同一消息。幂等消费者可使用消息ID、订单号加事件类型等稳定键建立唯一约束,重复到达时返回已处理结果。去重状态与业务变更应尽量原子提交,并设计保留周期、并发冲突处理和监控。对外部不可事务化副作用,还需业务幂等接口或补偿。
备考速记
至少一次就要接受重复;消息记身份,业务和去重一起提交。
原子事务在原子事务场景中的作用
库存表可以保存最后处理事件版本,或使用processed_message表对consumerId与messageId建立唯一约束。事务中先插入去重记录再更新库存,唯一冲突表示已处理,但两步必须共同提交或回滚。
同类题怎么考
- 分析确认丢失导致的消息重投。
- 选择去重记录与业务更新的事务边界。
原子事务在系统架构设计师软考中的考法
题干出现“业务已成功、确认失败、消息重投”,不要选关闭重试。正确方向是消费者幂等,并检查去重记录是否和业务副作用处于同一一致性边界。
解题思路
沿时间线看:库存已经减了,确认却没送到,队列只能把M100当作未完成再发一次。消费者第二次收到时若先查到M100已经处理,就直接返回已有结果。关键还在“同一事务”:若库存扣了但去重记录没写,故障窗口仍在,所以选 C。
考点定位
消息队列的重复检测可以减少重复发送,但不能替代消费者幂等。真正要保护的是业务副作用。
易错提醒
- 只在内存Set中记消息ID,进程重启后记录丢失。
- 先写去重表再更新库存,两步不在同一事务中,失败后消息被错误跳过。
- 幂等记录永久不清理,数据量失控;清理过早又让迟到重投穿透。
备考提示
- 画出处理、提交、确认三个时刻,分别插入崩溃点。
- 把发送端去重、队列重复检测和消费者业务幂等分开复习。
你可能还想了解
- 消息确认丢失为什么会重复投递?
- 幂等消费者的去重表应怎样设计?
- 队列重复检测能替代业务幂等吗?
本文小结
消息处理成功但确认丢失会触发重投。消费者应按消息ID或业务键去重,并让去重记录与库存更新原子提交。