3.2 两个程序的爱情故事
沿请求构造、服务连接、消息传输、状态处理和响应接收追踪两个独立程序的协议边界,用延迟、丢失和重复副作用实验解释网络不可靠性。
学习目标
- 能沿构造请求、连接服务、传输消息、处理状态和接收响应追踪两个独立程序的一次交互
- 能用消息边界、连接生命周期、超时、重复和 request ID 解释网络为什么不能按本地函数调用推理
- 能在正常、延迟/丢失和重复副作用场景中定位首个偏离,选择幂等、去重或状态查询策略并重放
为什么需要这一机制
两个程序不共享同一块内存,也不共享一个调用栈。客户端必须把输入编码成消息,经由连接和不可靠网络传给服务端;服务端处理后再编码响应。延迟、丢失、乱序、连接中断和重复都会让客户端面对“不知道服务端是否已经执行”的状态。
核心合同
↡本页把两个程序重构为独立客户端和服务器借助 socket 与应用协议交换带边界消息的状态机,网络故障不能被隐藏。公式中的 state 位于服务端,不在客户端内存里。客户端收到响应只是获得一份表示;若响应丢失,必须用 request ID、幂等键或查询接口判断服务端是否已更新状态。
五个官方概念到机制证据
3.2 两个程序的爱情故事
↡由独立客户端、服务器、消息协议和不可靠网络组成的请求-状态-响应交互模型。它不是把两个函数换到两台机器,而是要求边界上的消息、连接、状态和失败都可观察、可重放。
好感
↡客户端发起连接并根据协议建立可用通信关系的阶段,关系建立本身有超时、认证和中断边界。连接成功不等于业务请求已发送。记录地址、握手、认证、连接 ID 和超时,区分连接失败与服务拒绝。
分离
↡客户端和服务器拥有独立进程、地址空间和生命周期,必须通过序列化消息而非共享调用栈交换数据的边界。分离意味着版本、编码、消息 framing 和断连恢复都要进入合同;任何一端重启都不应让另一端依赖隐含内存状态。
网络
↡承载消息但可能延迟、丢失、重复、乱序或中断的通信环境,要求应用明确超时、重试和幂等策略。网络不是透明管道。实验要改变延迟、丢包或连接状态,记录客户端与服务端各自看到的事件。
Web
↡在客户端与服务器之间用开放应用协议表达资源、请求、响应和状态的交互层。Web 把消息语义建立在连接之上,但不消除分布式不确定性;状态码、表示、request ID 和业务查询一起形成恢复合同。
五个节点到机制证据
构造请求
↡客户端把输入、方法、版本、认证、request ID、幂等键和请求体组织成有边界的协议消息。保存消息摘要、长度、编码和身份;请求构造正确不等于网络已经送达。
连接服务
↡客户端解析服务地址并建立到服务器的连接,记录握手、认证、超时和连接生命周期。区分连接成功、连接被拒绝和连接中断;连接复用时仍要记录消息所属的 request ID。
传输消息
↡应用消息在传输层穿过独立进程边界,按 framing、顺序和完整性规则发送和接收字节。一次读写不一定对应一次完整消息。记录部分读写、重传、顺序、长度和校验,直到得到明确完整或拒绝结论。
处理状态
↡服务端解码请求、执行状态转换并记录提交或拒绝结果,副作用与响应发送是两个可分离事件。服务端要用 request ID 去重或查询,保存状态提交点和副作用计数;响应未到达不应抹掉已提交事实。
接收响应
↡客户端按消息边界接收并验证服务端表示,将成功、失败或未知状态映射为下一步动作。验证响应版本、长度、状态和 request ID。超时时进入查询/恢复状态,不把空响应当成服务端未执行。
最小可重放实现
request = encode(payload, requestId, idempotencyKey)
connection = connect(service, timeout)
sendFramed(connection, request)
response = receiveFramed(connection)
assertTrace(request, connection, serverState, response)
assertTrue(resetAndRun() == baselineTrace)这段草图只表达独立进程间的消息合同,不复制书中叙事或代码。实际复核应保存 framing、连接状态、请求身份、服务提交、副作用计数和响应。
五步复核一次程序间交互
1. 构造带身份的请求
固定 payload、协议版本、request ID、幂等键和消息 framing,先预测服务端看到的完整输入。
Lab
消息 framing 与重复副作用实验
一次只改变网络时序、消息完整性或幂等策略,观察两端状态如何收敛。
消息完整到达,服务提交一次并返回响应
R1 → connect → framed request → commit=1 → framed response
判定
通过:两端事件按 request ID 对齐,副作用计数为一次
当前场景:稳定交互;记录 request ID、连接、消息帧、服务提交、响应、重试和副作用计数。
正常、边界与故障证据
| 场景 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | 消息完整、连接稳定、服务提交并返回 | 请求-状态-响应路径完整且可重放 | framing、连接、状态、响应 |
| 边界 | 延迟、部分读写、连接中断或响应丢失 | 状态进入可查询的未知/恢复分支 | request ID、读写、提交、查询 |
| 故障 | 非幂等消息超时后重复发送 | 去重或拒绝重复副作用,不能静默双写 | 幂等键、服务日志、副作用计数 |
专属因果实验
先预测构造、连接、传输、处理和接收五个节点,再运行稳定网络基线;然后一次只切换延迟、部分消息、响应丢失或重复重试。实验显示 request ID、消息帧、服务状态、响应和副作用计数,避免把“没收到”误判成“没执行”。
Lab
消息 framing 与重复副作用实验
一次只改变网络时序、消息完整性或幂等策略,观察两端状态如何收敛。
消息完整到达,服务提交一次并返回响应
R1 → connect → framed request → commit=1 → framed response
判定
通过:两端事件按 request ID 对齐,副作用计数为一次
当前场景:稳定交互;记录 request ID、连接、消息帧、服务提交、响应、重试和副作用计数。
故障诊断:用 request ID 对齐两个进程
- 核对请求构造:比较两端协议版本、长度、编码、request ID 和幂等键。
- 核对连接/传输:检查连接生命周期、部分读写、消息 framing、顺序和完整性。
- 核对服务状态:确认服务端是否解码、提交、去重或产生副作用,区分处理和响应发送。
- 核对客户端恢复:响应丢失时查询或安全重试,清空两端状态后重放并验证只发生一次副作用。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 3.2 两个程序的爱情故事
独立客户端和服务器通过消息协议在不可靠网络上交互的模型。
- 好感
客户端建立并维护到服务器通信关系的连接阶段。
- 分离
两个进程拥有独立地址空间和生命周期的边界。
- 网络
可能延迟、丢失、重复、乱序和中断的消息承载环境。
- Web
用开放应用协议表达资源、请求、响应和状态的交互层。
- 构造请求
编码 payload、身份、认证和 framing 形成协议消息的阶段。
- 连接服务
建立到服务器的连接并记录握手、认证和生命周期的阶段。
- 传输消息
按应用层 framing 在独立进程间传递完整字节的阶段。
- 处理状态
服务端解码请求、提交状态并管理去重和副作用的阶段。
- 接收响应
客户端验证响应并决定完成、查询、重试或未知状态的阶段。
练习
练习
问题 1(3.2 两个程序的爱情故事、好感): 为什么连接成功不能证明请求已经被服务端处理?
问题 2(分离、网络): 为什么一次 socket 读取不能直接当成一个完整业务消息?
问题 3(Web): 响应丢失后如何判断服务端是否已经产生副作用?
本页小结
3.2 两个程序的爱情故事的关键不是把远程调用讲成情感故事,而是沿构造请求、连接服务、传输消息、处理状态和接收响应保存两个进程的证据。完成标准是处理延迟、部分读写、响应丢失和重复副作用,在首个边界修复 framing、幂等或查询策略,并用双端清空后的重放验证。