后端面试里,“怎么保证接口幂等”经常被问得很轻:有人回答加 token,有人回答加分布式锁,还有人回答前端按钮置灰。这些手段都可能有用,但它们不是幂等的核心。真正可靠的幂等,是同一个业务动作重复到达时,系统仍然只产生一次正确结果。
重复请求并不只来自用户双击。网关可能重试,消息可能重复投递,第三方回调可能多次到达,补偿任务也可能反复执行。只要系统分布在多个服务和多个网络调用里,“同一件事来多次”就是常态,不是异常。
先找到业务唯一键
幂等的第一步不是加锁,而是找到这次业务动作的唯一身份。支付场景可能是支付流水号,提交申请可能是申请单号加操作类型,消息消费可能是消息 ID 加业务单据,接口请求可能是客户端生成的 requestId。
这个唯一键不能太粗,也不能太细。太粗会把不同业务动作误判成重复,太细又挡不住同一动作的多次到达。比如订单支付和订单取消不能共用一个简单订单号作为幂等键,因为它们是不同动作;同一次支付回调却应该能被同一个支付流水识别出来。
状态机比锁更重要
锁能减少并发冲突,但不能替代业务状态。真正稳定的后端系统,会限制状态只能沿合法路径流转。订单可以从待支付到支付中,再到支付成功或支付失败;不能从支付成功又被重复回调改回支付中,也不能让取消和支付成功同时生效。
状态机的价值在于,即使重复请求绕过了某个入口,最终写数据库时仍然会被条件挡住。比如更新语句带上当前状态条件:只有待支付才能进入支付中,只有支付中才能确认成功。这样重复回调再次到达时,状态已经不是可变更状态,系统可以直接返回已处理结果。
幂等结果要能被查询
很多实现只挡重复写,却没有处理重复请求的返回。用户第一次提交后网络断开,第二次带同一个幂等键重试,系统不应该简单返回“重复请求”,而应该返回这个业务动作当前的结果:处理中、成功、失败或需要人工确认。
这也是为什么幂等记录最好和业务状态有关联。只记录一个 key 是否存在,排查时很难知道它对应什么业务结果。记录请求参数摘要、业务单号、处理状态、首次处理时间和最后一次请求时间,会更利于后续定位。
面试里更成熟的表达
可以这样回答:我会先为业务动作设计幂等键,再用唯一约束或幂等表挡住重复进入;核心状态通过状态机和条件更新保证只能合法流转;重复请求返回已有处理结果,而不是制造新的副作用;失败场景通过补偿任务重试,但补偿任务本身也必须幂等。
这个答案比“加锁”更接近真实系统。因为锁解决的是某个瞬间的并发,幂等和状态机解决的是整个业务生命周期里重复、重试、回调和补偿共同造成的不确定性。
还有一个容易被忽略的细节是参数摘要。同一个幂等键如果带着完全不同的请求参数再次进来,不能直接当成同一次请求成功处理。更稳的做法是保存关键参数摘要,重复请求到达时先比对摘要:一致就返回已有结果,不一致就拒绝并提示幂等键冲突。这样可以避免客户端复用 requestId 导致错误业务被吞掉。
面试里主动补到这里,会比只讲唯一索引更有说服力。它说明你知道幂等不只是防重复,也是防止“看起来重复、实际不是同一动作”的脏请求进入核心状态机。