用户说只点了一次提交客服却看到两笔一模一样的订单。前端按钮确实做了防抖后端也查过“这个用户有没有下过单”可两笔订单还是都进了数据库。问题往往不是请求重复了而是系统把同一次“下单意图”当成了两次独立操作。1. “我只点了一次”请求却可能到了三次用户看到的是“点了一次”系统看到的却可能是一条很长的调用链用户点击 → 前端发出请求 → 网关转发 → 订单服务处理 → 调用支付或库存服务这条链上任何一段超时都可能触发重试。客户端等 3 秒没响应可能自动重发网关调用订单服务超时可能再发一次服务之间的 RPC 框架开启了重试也可能再发一次。用户那边只看到一个转圈的按钮后台却已经收到了好几份内容相同的请求。10:00:00 请求 A 到达开始创建订单 10:00:03 客户端等不及重发请求 B 10:00:04 请求 A 处理完成订单 1001 创建成功 10:00:05 请求 B 到达又创建了订单 1002这两笔订单的内容可能完全一样同一个用户、同一件商品、同一个金额、同一个收货地址。从数据库角度看它们就是两次合法的插入。前端的按钮防抖只能减少“用户手抖点两下”管不住客户端超时重试、网关重试和消息重投。真正的防线必须放在服务端而且不能只靠一次SELECT判断。图 1重复请求可能来自点击、超时重试或消息重投最后都要经过服务端的幂等判断和数据库唯一约束。2. 幂等不是“拦住第二次请求”很多人把幂等理解成“第二次请求直接拒绝”。这只说对了一半。幂等更准确的意思是同一个操作执行一次和执行多次业务结果相同。比如“把订单状态改成已支付”执行一次和三次最终状态都是已支付这就是幂等。“创建一笔订单”如果每次执行都插入一条新记录就不幂等只有让第二次执行返回第一次的结果或者什么都不做才叫幂等。所以幂等的目标不是让第二个请求消失而是让它不产生第二份业务结果。第二个请求可能仍然会到达服务器仍然会走一遍接口但它应该被识别出来然后返回一个合理的结果。这个结果可以是第一次已经成功直接返回第一次创建的订单第一次还在处理中返回“处理中请稍后查询”第一次失败了根据失败原因决定是否允许重试。只返回一句“重复提交”并不理想。客户端不知道第一次到底成功没有可能还会再试一次问题反而更复杂。3. 为什么“先查再插”挡不住并发最常见的错误写法是这样的publicOrdercreateOrder(CreateOrderCommandcmd){OrderexistingorderMapper.findByRequestId(cmd.getRequestId());if(existing!null){returnexisting;}OrderorderbuildOrder(cmd);orderMapper.insert(order);returnorder;}单线程下这段代码没问题。两个请求同时到达时情况就不一样了请求 A查询没找到 请求 B查询也没找到 请求 A插入订单 1001 请求 B插入订单 1002两个请求都在对方插入之前完成了查询所以都认为自己可以创建。这就是典型的“检查后执行”竞态。查询和插入之间有一段空档并发请求正好从这段空档里挤了进来。要解决它不能只靠应用层的if必须让数据库来保证“同一个键只能存在一条记录”。4. 幂等键应该由谁生成幂等键是识别“这是不是同一次操作”的凭证。它必须满足两个条件同一次业务意图的多次重试共用同一个键不同业务意图使用不同的键。比较常见的做法是让客户端生成POST /api/orders Idempotency-Key: 9f1c2d7e-4a3b-4c8d-9e10-2f6a7b8c9d01 Content-Type: application/json客户端在用户点击“提交订单”时生成一个 UUID之后不管是超时重发还是用户手动重试都复用这个键。只有用户明确开始了一笔新订单才生成新的键。如果让服务端在每次请求里生成键那就没有意义了每次重试都会得到一个全新的键系统仍然会把它们当成不同操作。如果键里带了时间戳或随机数也会出现同样的问题。键的内容要能区分业务范围。比如order:create:用户ID:客户端请求ID比单纯一个 UUID 更好排查。关键是同一个用户、同一次下单意图重试时键不能变。5. 最稳的落点数据库唯一约束不管前面用了 Redis 还是本地缓存做预检查最后都应该有一道数据库唯一约束。因为 Redis 可能过期、可能被清空应用也可能在两步之间崩溃只有数据库的唯一索引能对“最终只能有一条”负责。可以建一张幂等记录表CREATETABLEidempotency_record(idempotency_keyVARCHAR(64)PRIMARYKEY,statusVARCHAR(16)NOTNULL,order_idBIGINT,created_atDATETIMENOTNULL,updated_atDATETIMENOTNULL);处理流程可以写成这样TransactionalpublicOrderResultcreateOrder(CreateOrderCommandcmd,Stringkey){try{idempotencyMapper.insertProcessing(key);}catch(DuplicateKeyExceptione){IdempotencyRecordrecordidempotencyMapper.findByKey(key);if(SUCCESS.equals(record.getStatus())){returnorderService.getResult(record.getOrderId());}thrownewDuplicateRequestException(请求正在处理中);}OrderorderorderService.create(cmd);idempotencyMapper.markSuccess(key,order.getId());returnOrderResult.of(order);}这段代码的关键在第一步插入幂等记录时数据库主键或唯一索引会替我们做原子判断。第一个请求插入成功继续创建订单第二个请求插入时触发DuplicateKeyException于是去读第一条记录返回已有结果或“处理中”。这里有一个很重要的细节幂等记录和订单数据最好在同一个数据库事务里。如果订单插入成功、幂等记录没写进去应用又崩了下一次重试就找不到“已经创建过”的证据。反过来如果幂等记录写成功了、订单却没创建记录会卡在PROCESSING需要超时清理或状态补偿。同一个事务能保证“订单和幂等记录一起成功或一起失败”是更简单也更可靠的方案。如果订单创建还调用了外部支付、库存服务数据库事务管不到外部系统那就需要状态机和补偿任务不能指望一个本地事务解决所有问题。上面的写法是一个便于理解的 MySQL MyBatis 版本。如果你用的是 JPA 或 PostgreSQL唯一键冲突可能会把当前事务标记成只能回滚冲突之后继续查询会报错。这种情况下插入幂等记录要放到独立事务里例如REQUIRES_NEW冲突后先结束那个事务再查询已有记录。6. Redis 可以做预检查但不能当最后一道锁Redis 的SET key value NX EX很适合做快速防重。第一个请求设置成功后续请求设置失败就能在进入数据库之前挡掉大部分重复流量。但它不能单独承担幂等责任键可能过期业务还没处理完第二个请求就能重新进来Redis 可能被清空或主从切换丢数据应用在SET成功之后、数据库提交之前崩溃键还在但订单没创建锁释放得比数据库提交早另一个请求可能在提交前进入。所以 Redis 更适合做“前置减流”数据库唯一约束才是“最终裁决”。两层配合的逻辑是Redis 挡住绝大多数重复请求数据库保证极端情况下也不会出现两条记录。如果使用分布式锁还要注意锁的持有时间要覆盖业务关键阶段删除锁时要校验持有者不能用固定的值直接DEL。但这些细节只能降低并发窗口不能替代唯一约束。7. 状态流转也可以用条件更新做幂等不是所有幂等都发生在“创建”阶段。支付回调、订单状态变更、库存扣减也经常重复到达。以支付回调为例同一个支付成功通知可能被发送多次。比起先查询再判断更稳妥的方式是带上状态条件更新UPDATEordersSETstatusPAID,paid_atNOW()WHEREid#{orderId}ANDstatusPENDING;如果影响行数是 1说明这次更新真的把订单从待支付改成了已支付如果是 0说明订单已经不是待支付状态可能之前已经处理过了。此时读取当前状态返回“已处理”即可。这种写法把“判断”和“更新”合并成了一个原子操作不需要在应用层先查一遍再改。它特别适合状态机PENDING → PAID、PAID → SHIPPED这种单向流转重复执行不会把状态倒回去。8. 消息重复消费是同一类问题消息队列通常保证“至少投递一次”而不是“只投递一次”。消费者处理完消息、还没来得及确认消费者重启了消息就可能再次投递。处理方式和接口幂等一致给每条消息一个稳定的消息 ID在消费时先写去重表或依赖业务唯一键。业务处理和去重记录在同一个事务里提交提交成功后再确认消息。如果先确认消息再处理业务消费者在确认后崩溃消息就丢了如果先处理业务再确认重复投递时又可能重复处理。正确的顺序是业务和去重记录一起提交最后再确认。这样即使确认失败、消息重投第二次也会被去重记录挡住。9. 怎么验证幂等真的有效幂等设计不能只看代码“看起来对”要做几种并发和故障测试同一个幂等键两个线程同时请求最终只能有一条订单同一个幂等键第一次成功后再次请求返回同一笔订单第一次处理到一半应用重启重试后不会产生第二笔不同幂等键、相同业务内容是否应该被视为重复要看业务规则消息重复投递两次消费结果只生效一次。最后一种情况要特别注意如果只靠幂等键而客户端每次重试都生成新键系统仍然会重复创建。幂等键只能解决“同一次意图的重复”解决不了“两次真实的新意图”。业务上是否允许同一用户重复下单需要在业务规则里明确。总结重复提交的根源是网络和调用链都可能重试而业务代码把每次到达都当成了一次新的操作。前端的按钮防抖只能减少误点真正的幂等要落在服务端客户端为同一次业务意图生成稳定的幂等键服务端用数据库唯一约束做最终判断Redis 只能做前置减流。创建类操作靠幂等记录状态类操作靠条件更新消息消费则把去重记录和业务处理放在同一个事务里。目标不是阻止第二个请求到达而是让第二个请求不产生第二份结果。参考资料Stripe API: Idempotent requestsMicrosoft Azure Architecture Center: Idempotency
企业数字化 ERP 产品动态
相关推荐
大模型持续学习:灾难性遗忘、EWC 与 LoRA 参数隔离 1. 面试现场:一个看似简单的问题
面试官问你:
一个大模型微调,先微调了法律知识,又微调了医疗知识,结果法律能力全丢了,加数据混合能解决吗?
你回答:不能。旧数据拿不到,… · 2026/11/3 13:41:03
小白程序员必看:用一条线梳理 Prompt、RAG、MCP、Agent 等AI核心概念 本文深入浅出地梳理了AI领域的关键概念,从Token和上下文窗口的基础知识,到Prompt Engineering、RAG、MCP等进阶技术,再到Agent的自主任务执行能力,旨在帮助初学者系统掌握AI技术的发展脉络和应用场景,是小白程序员进阶… · 2026/11/12 0:38:54
装机后怎么快速重建个人工具环境:清单法比盲目重装更稳 装机后怎么快速重建个人工具环境:清单法比盲目重装更稳
新电脑或重装系统后,最浪费时间的不是下载,而是想不起重装哪些、版本对不对、配置在哪。用清单法能少踩很多坑。
1. 先列「必须有」而不是「曾经装过」
分成三层:
• 开机就… · 2026/11/11 0:25:33
内蒙应急厅特种工证书过期了怎么办?真实经历告诉你复审延期全攻略 内蒙应急厅特种工证书过期了怎么办?真实经历告诉你复审延期全攻略 证书过期了不知道怎么复审延期,这事儿真的让很多人头疼。上周,我在内蒙古某地的建筑工地上,遇到了一位老电工小李,他手里拿着一张“过期”的低压电工证,焦急地问我:“现在这个证不能用… · 2026/11/15 13:27:53
建筑特种工挂靠良心建议:别被中介坑了,这才是正确打开方式 建筑特种工挂靠良心建议:别被中介坑了,这才是正确打开方式 你是不是还在为不知道去哪里报名特种作业证,怕被中介坑而发愁?别急,这篇文章就是为你准备的【建筑特种工挂靠】的良心建议,从报考到挂靠,再到后续发展,全都给你讲清楚。… · 2026/11/15 13:27:53
平潭电工证培训机构复审多少钱别踩坑 这样做稳过率高 平潭电工证培训机构复审多少钱别踩坑 这样做稳过率高 证书过期了不知道怎么复审延期?你不是一个人在战斗,很多电工都遇到过这个问题。尤其像在南阳卧龙区从事市政公用工程的朋友,电工证过期后不仅影响工作机会,还可能面临违规操作的风险。今天就来聊聊… · 2026/11/15 13:27:53
石家庄栾城区电工证2026河南最新政策实操考试避坑指南 石家庄栾城区电工证2026河南最新政策实操考试避坑指南 实操考试心里没底怕挂科?2026河南最新政策一出,很多准备考石家庄栾城区电工证的朋友都开始焦虑。尤其是那些打算去漯河参加培训的学员,面对政策变化、考试难度、培训机构选择等多重压力,心里… · 2026/11/15 13:27:53
会东报考电工证怎么考?2026河南最新政策全解析 会东报考电工证怎么考?2026河南最新政策全解析 实操考试心里没底怕挂科,这几乎是所有准备报考电工证的学员共同的焦虑。特别是在2026年河南最新政策出台后,考试标准和流程都有所调整,很多学员对考试内容、题型分布、复习方法等都不太清楚。本文将… · 2026/11/15 13:27:53
无电工证录用协议怎么签?网上查询流程全知道 无电工证录用协议怎么签?网上查询流程全知道 证书过期了不知道怎么复审延期,网上查不到信息,很多电工朋友都遇到过这个问题,尤其是商丘地区中小施工企业的负责人,面对项目急催、证书到期的情况,常常手忙脚乱。无电工证录用协议虽然听起来不常见,但在实… · 2026/11/15 13:26:53
长春初级电工证怎么考?通过率揭秘全在这里 长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/15 0:00:50
国内电工证报考条件与考试时间 低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/15 0:00:50
电工作业时如何断电?电子证书到底能不能全国通用? 电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50
长春初级电工证怎么考?通过率揭秘全在这里 长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/15 0:00:50
国内电工证报考条件与考试时间 低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/15 0:00:50
电工作业时如何断电?电子证书到底能不能全国通用? 电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 1:51:20
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 4:02:19
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 3:02:05