☰
接口超时之后,FDE为什么不能直接再试一次
2026/10/21 20:45:18 网站建设 项目流程

FDE岗位实务指南 · 第18篇

上一篇的四道检查管的是"动作发出之前"。这一篇处理动作发出之后的硬情况:请求发出去了,响应没回来。

页面转圈,然后提示超时。此刻用户的直觉动作只有一个:再点一次。产品团队的直觉通常也一样:在重试按钮旁边加个倒计时,或者在客户端代码里写个"失败自动重试三次"。这个直觉在读操作的年代是无害的——再查一次库存,查两遍也没事。但在写操作的年代,"再点一次"可能是双倍扣款、两张采购单、两条重复的核验记录。第 16 篇那张没展开的故障卡三,就停在这个路口:客户端显示超时,服务端到底执行了没有?

不知道。而且这就是唯一诚实的起点。超时不是"失败",超时是"未知"——客户端与服务端之间的应答断了,动作本身却可能已经走完。把"未知"当成"失败"处理,直接重试,是这个场景里代价最高的思维错误。

一、超时以后发生了什么,先不要猜

先把超时那一刻的可能性摊开。请求发出后,响应丢失可能发生在三个位置:请求没到达服务端(网络中断在去的路上),服务端收到并执行了但响应丢在回来的路上,服务端执行了一半卡住。三种情形的业务后果完全不同:第一种重试是安全的;第二种重试就是重复执行;第三种连"该不该重试"都没法直接回答。

用户看到的现象只有一个——页面显示等待。现象一模一样,背后的世界有三种,而处理动作取决于你在哪个世界。这就是为什么"先不要猜":猜错了代价是业务数据,猜对了没有奖励,因为没人知道你猜过。

这里面的关键是一种代价的不对称。查一下再决定,成本是几分钟的等待和一次查询调用;猜错了直接重试,成本是一笔重复的业务记录,以及随之而来的对账、冲正、解释和信任损耗。两笔账摆在一起,"先查再说"几乎是唯一理性的选择——可它违反直觉,因为等待是被动的,重试让人感觉"在解决问题"。好的机制设计,就是要让理性的选择成为默认路径,让直觉的选择走不通。

正确的第一反应是承认"我不知道结果",然后把这个未知当成一种需要专门处理的状态,而不是一个需要立刻消灭的异常。还有一种常见误判值得点名:把超时当性能问题。"系统太慢了,优化一下"——优化或许有必要,但它回答的是另一个问题。哪怕响应快如闪电,只要网络上存在断点,响应丢失就永远可能发生。性能优化降低超时的频率,机制设计消除超时的歧义,两件事都做,但别指望前者替代后者。

澄川制造第 16 篇留下的那张卡(教学合成):核验员甲对 A-017 v2 提交确认,客户端超时,页面仍显示"等待中"。甲没有重试——因为系统在这个位置的设计不是"允许重试",而是"进入待查询"。这个设计选择,就是这一篇要讲的东西。

二、把业务动作和请求标识对应起来

处理未知状态的前提是:事后能查。而"能查"的前提是,每一个业务动作从发出那一刻起就携带着一个稳定的、唯一的请求标识。

这个标识要解决的核心问题叫重复控制。同一个业务意图——"甲确认 A-017 v2"——无论被提交一次还是五次,系统都应该只执行一次。实现的方式是幂等:请求携带业务级标识(澄川的约定:请求 K-17 关联"A-017 + v2 + 确认动作 + 甲"),服务端执行前先查这个标识——没见过,执行并记录;见过,直接返回第一次的结果,不再执行。第一次成功、第二次返回既有结果,对用户来说两次体验一致;对业务数据来说,只发生了一次动作。

"幂等"这个词听起来唬人,意思很朴素:同一个操作执行一次和执行多次,效果一样。电梯的关门按钮就是幂等的——按一下和按十下,门都是关一次。而"提交订单"天然不是幂等的,按十次就是十张订单,除非系统专门做了设计:用标识识别出"这十次是同一个意图",只执行第一次,后面九次原样返回第一次的结果。重复控制的所有工程,就是把非幂等的业务动作,包上一层幂等的外壳。

标识本身怎么设计,直接决定这层外壳靠不靠谱。太粗——比如只用"用户 + 动作类型"——用户正常提交两笔不同业务也会被误判成重复;太细——比如带上时间戳——重试时时间戳变了,标识就识别不出是同一个意图,幂等失效。正确粒度是"业务上算作同一笔"的那些要素的组合:谁、对哪个对象的哪个版本、做什么动作、关键参数是什么。

这里有三条纪律,每条都对应一种真实出过的错。

其一,标识必须是业务内容决定的,不能是会话随机数。同一个业务意图重试时标识相同,不同业务意图标识不同——"甲确认 A-017 v2"和"甲确认 A-017 v3"必须是两个标识,因为 v2 和 v3 是两笔不同的业务。同一请求标识被复用于不同业务内容,就会把后一笔业务错当成前一笔的重复,直接返回旧结果——这种错误比重复执行更隐蔽,因为表面上一切"成功"。

其二,标识要绑定版本。第 17 篇讲的版本一致性在这里延伸一步:K-17 绑定的是 A-017 的 v2,如果查询期间提案变成了 v3,那么"基于 v2 的确认"这个意图本身已经失效,正确处置不是重试,是回到第 17 篇的版本拒绝路径。

其三,标识要有去处:请求记录、执行记录、目标对象,三者能互相找到。查 K-17 能查到它有没有执行记录;查执行记录能查到它改了哪个对象;查 A-017 能反查出关联它的所有请求。三张牌少一张,"查询"这个动作就断链。

这三张牌其实就是第 17 篇证据五要素在恢复场景的倒影:执行端写证据时想到的"以后要查",在这一篇兑现了。设计执行证据时不考虑恢复查询,恢复时就会发现查无此据;设计恢复机制时不核对证据字段,查询时就会发现缺关键一环。两头是同一张图纸的正反面,分两次画就会对不上。

三、查询结果并处理重复请求

有了标识,超时后的标准动作是查询,不是重试。澄川这张恢复卡(教学合成)是这样填的:请求 K-17,关联 A-017 v2 确认动作;客户端超时;查询执行记录——核验记录已保存,确认动作实际已成功;处置是向客户端返回原记录、把页面从"等待中"更新为"已确认";明文约束:不得再次创建同一结果。

注意这个处置里没有"重试"。查询发现已执行,剩下的事只是把结果如实呈现给用户——这叫恢复,不叫重试。恢复卡上还写了一栏容易漏的内容:如果查询这条路也断了(执行记录服务恰好也不可用),接手人是谁、查证材料是什么——系统负责人接手,查证材料是服务端日志与数据库中的核验记录表,在查询恢复前,该申请单的确认入口对所有人临时锁定,防止任何人在未知状态下再制造一笔。

恢复卡里还有一个细节值得停下来看:"返回原记录并更新页面"这十个字,意味着结果要可追溯回原物。不是重新生成一份"看起来一样"的结果给用户,而是把服务端已保存的那份核验记录本身取出来、展示出来。重新生成会引入第二次不一致的机会——生成的和保存的万一有出入,用户看到的就是假象。恢复原物,看到的就是事实本身。

查询的结果不止"已成功"一种,决策表要覆盖全部分支:已知已成功——返回既有结果,不得重复执行;已知明确失败(服务端有失败记录)——业务条件重新校验(状态、版本还在不在)后允许重试;已知明确拒绝——走第 17 篇的拒绝路径,不重试;仍然未知——转人工核查,限制一切可能产生重复副作用的操作,直到查清楚。四种分支四种动作,唯独没有"默认再试一次"。这张表要贴在每个相关角色的手边——客服接到"我点了没反应"的求助时,照表第一步就是查询标识,而不是指导用户"您再点一次试试"。

把这些分支整理成一张超时处置决策表,字段是:已知状态(已成功/已失败/已拒绝/未知)、查询方式(按请求标识查执行记录)、可重试条件(明确未执行且业务条件重新校验通过)、接手人(未知状态转系统负责人)、恢复依据(执行记录与服务端日志)。这张表和第 16 篇的排障卡、第 17 篇的授权矩阵是同一类东西:把"到时候再说"的临场判断,提前变成"照表执行"的既定动作。事故现场最不缺的资源是慌乱,最缺的资源是判断力——决策表就是把判断力从事故现场提前存进文档里。

还要补一个视角:这套机制不只防用户手抖,也防系统自己的重试。消息队列的重投、定时任务的补跑、Agent 在结果不明时的"再试一次"——自动化的重试比人工重试勤快得多,破坏力也大得多。所有写入路径上的执行者,无论人还是系统,都走同一个幂等闸门,这是这套设计里最不能讨价还价的部分。

特别要点名 Agent 场景:模型在结果不明时的默认倾向是"再调用一次工具",因为这最像"继续完成任务"。第 17 篇给 Agent 立的规矩是"建议而非直接执行",这一篇要再加一条:Agent 的重试意图必须经过幂等闸门,且它的请求标识必须继承自最初那次业务意图,而不是每次调用重新生成——否则 Agent 的五次重试在系统眼里就是五笔新业务。

四、区分重试、恢复、补偿与人工处置

查清楚了,接下来的动作才谈得上选择。四个选项对应四种局面,混用是另一类事故。

四个选项的判别顺序是固定的:

1、先问"执行了没有"——没执行,考虑重试;

2、执行了,再问"结果送达了没有"——没送达,走恢复;

3、送达了或已可见,再问"这个结果对不对"——不对,走补偿;

4、最后问"这个动作可不可逆"——不可逆,直接转人工处置。

顺序不能乱:在"执行了没有"还没答案时谈补偿,和没确诊就开刀一样荒唐。

重试,适用于"明确未执行且业务条件仍允许"。两个条件缺一不可:没执行过(有证据),而且现在执行仍然合法(状态、版本重新校验过,因为超时之后世界可能变了)。

所以"重试三次"从来不是通用解法——它假设了所有失败都是"未执行",而这个假设在写操作场景里恰恰是错的。退一步说,即使确认了"未执行",重试前还有一道第 17 篇留下的手续:业务条件重新校验。超时发生在五分钟前,这五分钟里申请单可能已被他人确认、预算可能已被占用、提案可能已从 v2 变成 v3——五分钟前合法的意图,此刻未必合法。重试不是把旧请求原样再发一遍,是带着旧意图重新过一次当下的校验。

恢复,适用于"已执行但结果没送达"。动作不重复,只是把既有结果找到、返回、把各处的状态刷成一致。K-17 走的就是这条路。

补偿,适用于"执行了,但事后发现不该执行或执行错了"。关键认知:补偿是一个新的业务动作,不是把原动作删掉。采购申请已经下发,"撤销"不是删除那条记录,而是发起"作废采购申请"的新动作,由对方系统确认后再更新状态;原记录、作废记录、两者之间的因果,全部留在审计里。把补偿做成删除,等于篡改历史——审计要的是"发生过什么、怎么纠正的",不是"看起来什么都没发生过"。

人工处置,适用于高风险或不可逆动作。客户消息发出去了收不回,误触达就是事实,能做的是记录事件、生成安抚任务、触发合规报告。这类动作的正确设计是在建模时就和主动作一起把补偿路径想清楚,而不是等事故发生后临时发明。

补偿记录里要包含什么?原动作标识、补偿动作标识、两者的因果关联、补偿的发起人和理由、外部系统的确认结果。少任何一项,后来的审计者看到的都是两笔孤立的记录,看不出"这一笔是为了纠正那一笔"。补偿是叙事的另一半:原动作讲了"发生了什么",补偿讲"我们怎么纠正的",两半合在一起才是完整的业务事实。

还有一条边界要明说:外部系统的动作未必能原样回滚。写进 ERP 的采购单、发给供应商的确认,对方系统认不认你的"作废"、按什么规则认,取决于对方的接口能力——具体接口的幂等保证和回退能力,只能查那一家的官方说明,不能凭感觉假设。设计恢复方案时,把每个外部动作按"可直接撤销 / 可补偿 / 不可逆"分好类,这个分类表就是事故时的作战地图。

五、做一次响应丢失的受控演练

机制设计得再好,没在真实断点上验证过,都是纸上防线。澄川的做法(教学设定)是在隔离环境里做受控演练:用教学故障开关模拟"请求已保存核验记录、响应丢失",页面停在"等待中",然后观察两件事——用户侧有没有路径绕过"待查询"直接再次提交(有就是漏洞);系统侧查询、恢复、页面更新的全流程是否按恢复卡走通,走完之后核验记录、请求记录、对象状态三者是否一致。

演练本身也要留记录:哪天做的、模拟的是哪类丢失、四个分支各走了哪条路、发现的问题和修复。这份记录和排障卡、实验记录进同一个档案——它是下次审计"这套机制还活着吗"的证据,也是新人理解系统行为边界的教材。

演练同时要跑三个对照组:明确拒绝的请求(不应进入待查询,直接走拒绝路径)、明确成功的请求(正常闭环)、无法查询的请求(触发人工接手与入口锁定)。四个分支各过一次,这套机制才算从"设计过"变成"验证过"。演练不是一次性的:写入路径每次调整、外部系统每次升级、执行者每次新增(比如新接入一个 Agent),四个分支都要重跑。恢复机制和权限边界一样,是每次改动后需要重新确认的东西——第 17 篇那句"边界不是一次建成的",在这里原样适用。

迁移练习留给你,回到禾木家居:主管确认一条质检发现后客户端超时,如果是你设计,"创建辅导任务"这个动作的请求标识怎么定、重复创建的任务对组长的工作清单有什么影响、已下发给组长的任务算不算"已产生外部影响"?把已成功、已失败、未知三种状态分开,各写出可采取的下一步——写不出来的那一种,就是你要补的设计。

下一篇进入运行期:上线之后,怎样持续知道它还在好好工作——输入分布会不会漂移,人工推翻率会不会爬升,业务规则变了系统知不知道。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询