核心是用唯一业务标识+状态机驱动恢复动作,每次重试前先查businessid最新状态并按状态分支处理,确保操作本身幂等、依赖确定性存储、异步任务支持去重与生命周期管理。

在 Java 异常重试中实现幂等恢复逻辑,核心是让“重试操作本身不改变最终业务状态”,即无论执行一次还是多次,结果一致。这不能靠 catch 了再 try 一下解决,而要从状态设计、操作建模和上下文控制三方面入手。
用唯一业务标识 + 状态机驱动恢复动作
每次业务操作(如支付、下单)必须携带全局唯一 ID(如 businessId),并持久化记录该操作的当前状态(如 INIT、PROCESSING、SUCCESS、FAILED)。恢复逻辑不是“重新执行整个流程”,而是先查这个 businessId 的最新状态:
- 若已是 SUCCESS → 直接返回成功,跳过所有处理
- 若为 PROCESSING → 检查超时或心跳,决定是继续等待还是主动补偿(如查第三方结果)
- 若为 FAILED 且可重试 → 更新为 PROCESSING,再执行核心逻辑(此时需确保核心逻辑自身也幂等)
- 若无记录 → 视为新请求,走正常流程(注意防止重复提交,前端/网关层应做防重)
核心操作本身必须设计为幂等(非仅靠重试框架)
异常恢复的“重试”只是触发机制,真正安全的前提是被重试的方法具备天然幂等性。常见做法包括:
-
基于条件更新:用 SQL 的
UPDATE ... WHERE status = 'INIT',只有初始状态才允许变更,避免二次扣款 - 使用乐观锁版本号:每次更新带 version 字段,失败则重读再试,防止并发覆盖
- 写入前校验前置状态:比如退款前先查订单是否已退款,而不是“减余额”后补日志
-
用唯一约束兜底:对关键业务单据(如交易流水)加
UNIQUE(business_id, type),重复插入直接抛异常,由上层捕获后转为幂等返回
恢复逻辑不依赖临时内存状态,只读写确定性存储
不要在重试方法里用局部变量、ThreadLocal 或静态缓存保存中间结果。例如:
- ❌ 错误:重试前把用户余额读到变量里,重试时直接扣减 → 第二次重试会重复扣
- ✅ 正确:每次重试都从数据库读最新余额,再用 CAS 或条件更新完成扣减
- 补偿类恢复(如发消息失败后重发)也要查消息表确认是否已发送成功,而不是靠“上次没发就这次发”这种假设
异步恢复任务需自带去重与生命周期管理
如果恢复逻辑走异步(如 MQ 延迟重试、定时任务扫描),必须支持:按 businessId 去重调度 + 设置最大重试次数和过期时间。例如:
- 投递恢复任务时,以
businessId作为消息 key,确保同 ID 的任务被路由到同一队列分区,便于消费端做本地去重缓存(如 Guava Cache + 过期时间) - 任务表增加
retry_count和next_retry_at字段,每次执行前先原子更新,失败则递增计数;达到阈值后转入人工核查队列 - 避免“无限重试+无状态轮询”,那只会放大数据风险
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











