接口重复提交:幂等设计与并发控制的落地做法 | 技术知识 | 都图科技 DOUTOO
都图科技doutoo.com
首页/技术知识/开发实践

接口重复提交:幂等设计与并发控制的落地做法

开发实践 2026.10.11|约 8 分钟|软件事业部 出品

重复提交不是用户手快,而是系统没设计好。只要接口会被重试,就必须假设它一定会被执行两次。

一、重复是从哪来的

  • 用户行为:双击提交、返回再提交、网络慢反复点
  • 客户端重试:超时自动重试,但服务端其实已经成功
  • 网关与负载均衡:上游超时重发,请求被投递两次
  • 消息重投:队列至少一次投递,消费端重复执行
  • 定时任务:多实例同时触发,或上次没跑完又起一轮

只要链路上有任何一个环节会重试,"不会重复"就是假设而不是事实。

二、三种幂等实现,按场景选

方式适用注意点
业务唯一键 + 唯一索引订单号、支付流水、单据编号最可靠;冲突时返回已存在的结果,不要报错
幂等 Token表单提交、创建类接口进入页面时领 token,提交时校验并消费,需设过期
状态机审批、订单状态流转只允许合法跃迁,重复请求落在同一状态即忽略

推荐优先用唯一键:它由数据库保证,不依赖应用层的锁和缓存,最不容易被绕过。Token 适合没法天然生成业务键的场景。

三、并发下的库存与余额

幂等解决"同一次请求执行两遍",并发解决"两个不同请求同时改一条数据"。两者要分开处理。

  • 乐观锁:带版本号更新,update ... set qty=qty-1, ver=ver+1 where id=? and ver=?,影响行数为 0 就重试或失败。适合冲突不频繁的场景。
  • 条件更新:where qty >= 1,把判断写进 SQL,避免先查后改的竞态。
  • 悲观锁 / 行锁:热点数据、冲突频繁时用,但要注意锁范围和超时。
  • 串行化:把同一单据的操作投到同一队列分区,天然串行。

最忌讳的是"先 select 再 if 再 update",这段代码在并发下必然超卖。

四、前端也要做,但不能依赖

  • 提交后立即禁用按钮,请求返回再恢复(失败要恢复,否则用户没法重试)
  • 防抖只能挡住手快,挡不住刷新与重放
  • 提交成功后跳转到结果页,用 GET 展示,避免刷新重复 POST

前端的防护是体验优化,不是数据安全的防线。后端不幂等,前端做得再细也白搭。

五、怎么确认线上真的重复了

  1. 先看数据特征:同一业务键多条记录、创建时间相差几秒、操作人相同
  2. 查入口日志:同一 traceId 出现两次,还是一个请求被投递两次
  3. 看网关与客户端超时配置,确认重试策略
  4. 补监控:唯一键冲突计数、重复请求比例,异常上升就告警
幂等要写进接口文档

每个写接口标明是否幂等、靠什么保证、重复调用返回什么。不写清楚,调用方就会自己加重试,问题只会更多。

← 上一篇邮件安全网关:拦截、隔离与放行的三段策略