“因此客户端观察到某次尝试成功与服务保证“一个逻辑请求一次效果”不能混淆。持久身份、结果保留、会话过期与恢复协议决定后者成立多久、哪些请求可以安全续试。确定性状态机也只能约束自己的原子域;对外…”
形式陈述
RPC把一次远端操作呈现为请求与响应,但发送、交付和执行不是同一事件。本页固定可信、非恶意两端以及一个原子业务操作:每个被处理的尝试要么执行一次并形成结果,要么在无效果的条件下拒绝。不同尝试可能分别执行;本句没有跨重试去重承诺。请求携带逻辑身份,各次尝试另有尝试号;响应须完整解析并与约定的关联字段匹配。网络本身不提供这项关联或原子业务合同。
本文“成功”只表示客户端取得了按合同可报告的、某次尝试已完成的成功结果,不表示各次尝试合起来只执行一次,也不表示所有在途尝试都已结束。
客户端对一个逻辑请求记录状态。初始为未发送;开始可能到达服务端的尝试后变待定。收到合同规定的成功响应,才标为上述观察意义的成功;收到能够证明该逻辑请求未产生效果的明确拒绝,或在任何尝试发送前本地校验失败,才标为明确失败。已经可能发送而连接中断、响应截断或deadline耗尽时,没有足够业务证据就标为结果未知。待定表示仍在等待,未知表示本轮等待结束而实际效果尚未确定。
必须以整个逻辑请求的全部尝试汇总证据。若第一次尝试已可能执行,第二次连不上,后者不能把整体改为明确失败;第二次的一般拒绝也不能抹去第一次可能的效果,除非协议保证它是在报告同一逻辑请求的最终状态。通用ERROR、5xx或传输错误码本身不承诺“无业务效果”。
重试条件与结果判定分开。只有操作语义允许重复、协议有可靠的请求身份/去重合同,或有证据原尝试未执行时,才可按相应策略重新发送。新连接、新TCP序号、相同payload都不能充当跨尝试去重协议。持久去重与效果原子提交的既有责任见消息传递页,完整会话和恢复协议留给容错服务主线。
直觉
请求完成与客户端知道完成之间隔着一份响应。把响应丢掉,并不会把已经发生的业务动作撤销。超时越明确,只能越明确地说明“我没有及时拿到答案”,不能顺便证明答案是失败。
未知是一种有用的结果分类。它提醒调用者去查状态、等待已有尝试或走补偿流程,而不是把非幂等操作无条件再做一遍。
例子与边界
相同观察,对应不同事实
操作为add(10),初始余额100。轨迹A:请求未到,客户端最后超时,余额100。轨迹B:请求到达,服务端提交余额110,成功响应丢失,客户端同样超时。两条轨迹中客户端都只观察到发送开始、没有完整响应、期限结束。它必须给同样的本地判定,因此仅凭这些观察不能同时正确地把A判失败、B判成功。这与异步系统的有限等待不可区分性一致。
B中即使请求字节已被TCP累计确认,也只能推知对端TCP收到了连续字节;应用执行可能在确认之前、之后,或尚未发生。若确认本身丢失,客户端更不能用“没有ACK”证明请求没到。
重连不会续接业务知识
沿轨迹B重建连接,再发送一次无去重保护的add(10),余额可以变120;新连接保证新字节流的传输次序,却不知道旧请求身份。若换成合同明确的“把目标值设为7”,重复的预期效果可与一次相同,但两次响应、审计日志及与其他并发操作的交互未必相同。
RFC9110的幂等定义针对请求的预期效果,不要求每次响应完全一样,也不要求服务端不记第二条日志。是否可自动重试还须遵守具体方法与应用语义;本页不把所有POST或所有错误一概当作可以重试。
三类终点证据
完整且匹配的OK(id,attempt,110),按约定服务端只在该次尝试效果完成后返回,足以报告这份成功结果。它不能证明最终余额永远为110:若另一尝试已经在途,客户端收到这份OK后,另一尝试仍可能再执行并使余额变120。若接口承诺一个逻辑请求最终唯一的结果和一次效果,就必须补入相应持久身份/去重合同,不能沿用本页无去重模型作此承诺。
任何字节发送前因金额格式非法而本地拒绝,且没有更早的同逻辑尝试,足以判明确失败。
已经可能执行后,仅收到TCP RST、半条响应或deadline到期,均保留未知。取消请求已发出但没有“原操作未生效”的确认,同样不能改成明确失败。