← 返回题目列表

拿到锁后业务失败应该如何补偿?

中等 第 24 / 26 题 更新于 2026/10/01
分布式锁补偿幂等状态机
学习 AI 实战项目

简化版

拿到锁只保证临界区互斥,不保证业务成功或自动回滚。失败后应先用本地事务保存业务状态与补偿任务,再按幂等状态机重试、反向补偿或人工对账;释放锁前必须把“接下来由谁恢复”持久化。

详细版

把操作拆成可提交步骤并定义每步的成功事实、幂等键与补偿动作。数据库内的修改优先放进同一本地事务;跨服务步骤使用 Saga、事务消息或 Outbox 推进,失败状态落库后再释放锁,不能只在内存捕获异常。

补偿不是简单执行相反 SQL:退款、库存释放等都要校验当前状态和业务边界。重试采用退避、次数上限与死信队列,永久失败转人工;监控失败阶段、补偿积压、最大滞留时间和最终对账差异。

完整版教学

一、锁与事务解决不同问题

锁回答“此刻谁可以进入”,事务回答“一组写入如何原子提交”,补偿回答“跨边界部分成功后怎样收敛”。持锁期间调用三个系统,第二步失败时,第一步已经提交的事实不会因释放锁而消失。设计必须先画出提交点,而非把锁当成万能回滚器。

易错点:释放锁不是撤销业务;真正的恢复依据必须在锁消失前持久化。

二、先定义状态机与成功事实

以订单履约为例,可定义 INIT -> RESERVED -> PAID -> DONE,失败进入 CANCELING 或 MANUAL。每一步只允许从指定前态迁移,并记录 requestId、版本和外部流水号。这样进程重启后能从状态继续,而不是根据异常消息猜测做到了哪一步。

INIT --reserve--> RESERVED --charge--> PAID --confirm--> DONE
  |                   |                  |
  +------cancel-------+------refund------+--> CANCELED

三、锁内应该持久化什么

同库的业务写与补偿任务可放在一个事务:业务状态更新成功时,同时插入 Outbox/补偿记录。若事务回滚,两者都不存在;若提交成功,即使进程随后宕机,后台扫描器仍能继续。不要在提交后只向内存线程池投递补偿任务。

失败位置已有事实恢复方式
本地事务前无直接失败
本地事务后、远程调用前状态+任务异步继续
远程超时结果未知查询后幂等重试
补偿永久失败待处理记录人工对账

四、补偿为什么必须幂等

消费者可能重复投递,扫描器也可能与人工按钮同时触发。补偿使用原业务 requestId 派生唯一键,并以条件更新推进状态;退款接口传原支付流水与退款单号,库存释放限定 RESERVED -> RELEASED。重复执行应返回相同结果而非重复产生副作用。

UPDATE order_task SET state='CANCELED', version=version+1
WHERE id=:id AND state='CANCELING' AND version=:version;

五、超时未知不能直接反向操作

远程调用超时只说明没有收到响应,不代表对方没有成功。若支付请求其实成功,立即释放库存可能造成已付款无货;应先用幂等键查询对方结果,再决定前向重试还是补偿。没有查询接口时,要以对账文件或回调作为最终事实来源。

例如调用超时 3 秒,先查询 2 次、间隔 1 秒;仍未知则标记 UNKNOWN,冻结后续相冲突动作并进入延迟核对,而不是武断判失败。

六、重试、人工与观测边界

瞬时网络错误可指数退避重试,参数错误和业务拒绝不应重试。设最大次数和总时间预算,例如 1、2、4、8 秒四次后进入死信;人工处理必须沿同一状态机和幂等接口执行。监控按失败步骤分组,并展示补偿队列长度、最老任务年龄与对账差异。

锁释放异常也不能覆盖原业务异常。使用 finally 尝试安全释放并记录 owner 校验结果,但恢复流程以持久状态为准。

七、常见误区与追问

  • 误区:拿到锁后发生异常,数据会自动回滚。 锁没有事务语义。
  • 误区:补偿就是执行一条相反 SQL。 外部副作用和状态变化可能不可逆。
  • 误区:远程超时等于调用失败。 它属于结果未知,必须查询或对账。
  • 误区:补偿任务放内存线程池就够了。 进程宕机会同时丢失任务。
  • 追问:何时释放锁? 至少在业务状态和后续恢复任务可靠提交之后。
  • 追问:补偿一直失败怎么办? 有界重试后转死信与人工,并保留完整审计。
  • 追问:人工重跑如何防重复? 复用幂等键、条件状态迁移和唯一约束。

八、加强记忆

按“锁管并发、事务管本地、状态机管进度、补偿管跨系统”记忆。拿锁后先识别提交点,把业务状态与恢复任务原子落库;超时先查事实,所有重试和补偿都走幂等状态迁移,最后靠死信、人工和对账保证失败最终可见、可收敛。