两个示意节点组在分区时失去联系;恢复跨组路径后,传播才能继续。

Gossip 收敛需要明确前提

流言协议通过反复联系对等节点来传播信息。一部分交换丢失时,只要仍有节点保留该状态,后续联系就可能再次传播它。但最终收敛需要前提:有关节点必须最终可达,更新不能提前消失,合并规则也必须能够处理实际产生的状态。随机选择伙伴本身,并不保证所有节点在某个固定截止时间之前达成一致。 区分传播与状态调和 节点可以主动推送状态、拉取对方状态,也可以双向交换。这是协议设计选择,并非每条流言消息都必须具有对称结构。在连通良好的成员集合中,每个节点每轮只联系少量伙伴,就可能让一条小更新快速扩散。常见的对数级传播估计属于特定概率模型,不能用一次网络来回耗时直接推导最坏收敛期限,也不能把每轮联系几个节点误解成整份状态的传输成本恒定。 成员流言也不会自动让应用写入一致。Dynamo 使用流言维护成员信息,用向量时钟表达对象版本,再用 Merkle 树反熵修复副本差异。这些机制承担不同职责。成员协议中的 incarnation 编号用于区分同一个成员的状态代次,并不是任意业务数据都可以直接采用的因果版本。理解职责分离,比把所有最终一致行为统称为流言更有助于定位故障。 下面的模型明确假定每个键只有一个写者,版本严格递增。对它播种的单条更新,保留更大版本已经足够。如果相同版本却包含不同内容,当前规则只保留先看到的一方,结果就会依赖合并顺序。真实系统必须说明如何处理并发、重复投递、删除和重启身份,不能用传播最终到达的证据替代这些合并契约。 坏掉的样子 持续的网络分区会阻止更新跨越互不连通的组件。每一侧继续重复内部交换,也无法补上不存在的通信路径。恢复连接后,只有所需状态仍然存在,调和才有机会继续。删除同样需要明确表示和保留期限:墓碑过早到期,可能让旧副本重新带回被删除内容。版本向量描述版本关系,墓碑记录删除事实,两者作用不同,也不会自动保证所有业务写入都被完整保留。 运行缓慢或暂停的节点可能看起来已经故障。SWIM 将故障检测与感染式传播分开,并通过怀疑阶段给被怀疑成员一个使用较新代次进行反驳的机会。这是在其假设下减少误判,并非在任意长延迟下都能精确判断死亡。数据重新复制和归属迁移属于外围系统的策略,不能说每次传播怀疑都会必然触发一次重新分片或自动撤销已经完成的迁移。 修复本身也消耗资源。全量状态交换、过于频繁的轮次,以及大量节点同时重返集群,都可能压满链路或接收端。限制扇出、增量交换和控制恢复速率能够帮助减压,但开销还取决于状态大小,不能只数每轮联系了几台机器。重启版本必须遵守具体实现的身份规则:计数器归零可能让新声明显得陈旧,或造成相同身份的歧义;较小版本不会因为“重启过”就自然覆盖较大版本。 没有连通路径,流言无法越过。依据原文绘制机制;模型值与原文报告的观察不代表本次运行测试。 快速验证 保存为 check_gossip.py,使用 python3 check_gossip.py 执行。它只使用 Python 标准库,不打开网络连接。二十个字典代表节点,每次扫描顺序访问节点,并做双向合并。固定种子使当前运行时的选择可复现;一条消息可以在同一次扫描中经过多个连续联系,因此这不是所有节点同步发送、同步接收的真实网络轮次模拟器。 import random NODES, ROUNDS, SEED = 20, 12, 7 def merge(a, b): # One writer per key, with strictly increasing versions in this fixture. merged = dict(a) for key, (version, value) in b.items(): if key not in merged or version > merged[key][0]: merged[key] = (version, value) return merged def spread(peer_for, nodes=None): rng = random.Random(SEED) if nodes is None: nodes = [{} for _ in range(NODES)] nodes[0]["config"] = (1, "blue") history = [] for _ in range(ROUNDS): # Sequential contacts: knowledge can travel several hops in one sweep. for i in range(NODES): peer = peer_for(i, rng) if peer == i: continue united = merge(nodes[i], nodes[peer]) nodes[i], nodes[peer] = dict(united), dict(united) history.append(sum(state.get("config") == (1, "blue") for state in nodes)) return nodes, history random_peer = lambda i, rng: rng.randrange(NODES) nodes, history = spread(random_peer) assert history[-1] == NODES assert all(a <= b for a, b in zip(history, history[1:])) print("random contacts:", history) # A fixed hub is connected; fixed contacts do not inherently prevent convergence. _, hub = spread(lambda i, rng: 0) assert hub[0] == NODES print("fixed hub after one sweep:", hub[0]) # Two disconnected peer sets retain different views until contacts can cross. partitioned, split = spread(lambda i, rng: (i // 10) * 10 + rng.randrange(10)) assert split[-1] == 10 and all(not node for node in partitioned[10:]) _, healed = spread(random_peer, partitioned) assert healed[-1] == NODES print("partition:", split[-1], "after healing:", healed[-1]) # Dissemination alone cannot repair an ambiguous concurrent-write merge rule. a, b = {"config": (2, "red")}, {"config": (2, "green")} assert merge(a, b) != merge(b, a) print("equal-version conflict is order-dependent: not a multi-writer merge") print("PASS: dissemination, topology, partition, and merge controls") 随机联系产生的知情节点数依次为五、十四、十九,第四次扫描达到二十,后续保持不变。这复现了原先记录于 macOS、CPython 3.12 的单更新结果,但只能说明这个输入和调度,不是其他随机种子或线上集群的收敛概率界限。当前模型补充明确的反例,不再仅凭一次成功就推断任意拓扑和故障条件下的可靠性。 ...

九月 20, 2027 · 2 分钟 · XB Field Notes
客户端发送 SYN,序列号为 x;服务器发送 SYN-ACK,序列号为 y、确认号为 x+1;客户端发送 ACK,序列号为 x+1、确认号为 y+1。

TCP 握手建立序列号空间,不是同步两台机器的时钟

TCP 三次握手建立字节流的两个方向。每端提供自己的初始序列号,并确认另一端的序列号。这是两个独立序列空间,不是共享墙上时钟;应用建立连接不需要两台机器时钟同步。之后的序列号用于标识字节,支持连接内部的排序、重复处理、确认和重传。把序列号与时间混为一谈,会误解握手真正建立的协议状态。 图示普通握手及受控丢包场景;32 位序号按模回绕。connect 成功不等于应用已能提供有效服务。 坏掉的样子 客户端发送带初始序列号的 SYN,服务器回复 SYN-ACK,给出自己的初始序列号并确认客户端 SYN,客户端再确认服务器响应。即使没有应用数据,SYN 也占用序列空间中的一个位置,所以对应确认号增加一。将这些数值当成时间戳,会错误解释报文,还可能把排障引向无关的时钟同步问题,忽略真正缺失的确认或路由。 两端不会在完全相同的时刻改变状态。客户端收到 SYN-ACK 后,connect 可能成功返回,但最后一个确认在到达服务器前丢失。服务器仍可能停留在 SYN-RECEIVED,Linux 工具显示为 SYN-RECV。后续携带适当确认的数据也能完成交换,因此单独第三个报文缺失,并不必然导致永久失败,不能把抓包中的固定三包模式当成唯一合法过程。 TCP 建立成功也不代表应用健康或对端身份可信。服务可能接受连接却无法生成有效响应,代理可能完成客户端这一侧连接,却无法建立另一个独立上游连接。初始序列号选择有助于区分连接实例、抵御预测攻击,但不是身份认证协议,也不会加密字节流。传输状态、应用结果和安全身份需要各自对应的验证,不能由一项成功代替全部。 快速验证 将脚本保存为 check-tcp-handshake.py,准备 Docker 后运行 python3 check-tcp-handshake.py。一次性容器使用 Python 3.11.16、iproute2 6.1.0、nftables 1.0.6 和 tcpdump 4.99.3,在 Linux 6.12.76 上验证。NET_ADMIN 与 SYS_ADMIN 支持嵌套网络命名空间。客户端和目标由新建 veth 相连,不移动宿主接口或发布端口: ...

九月 10, 2027 · 3 分钟 · XB Field Notes
well-known 路径指向 security.txt 文档,其中字段仍需验证。

Well-known 路径提供的是发现约定

Well-known URI 为软件发现某个源站的信息提供约定位置。保留的 /.well-known/ 前缀,减少了不同协议各自设计路径时发生名称冲突的机会;注册后缀则指向定义它的具体规范。这个前缀是一种命名机制,不是自动实现,更不是内容可信的保证。服务器仍需正确路由所支持的端点,客户端也必须按照该端点自己的契约解释响应。 按具体后缀的规范实现 RFC 8615 为支持的 URI 方案保留这一命名空间,不要求每个网站发布所有已注册资源,也没有给所有后缀定义统一响应格式。确定媒体类型、认证、跳转处理、缓存或传输条件之前,应查看 IANA 注册及其引用规范。路径看起来熟悉,不能替代协议规则;不同自动化客户端也不会因为共用前缀,就自然采用相同的读取和验证方式。 对于通过网站发布漏洞联系信息,RFC 9116 规定 security.txt 位于 /.well-known/security.txt,并要求通过 HTTPS 获取。旧位置 /security.txt 可以重定向到标准位置。文档使用 UTF-8 纯文本和规定字段,包括至少一个 Contact 以及唯一的 Expires 字段。Contact 提供报告渠道,不代表邮箱一定有人值守,更不能证明某份报告已经被接收或处理。 文档中的 Expires 字段用时间戳限制信息的新鲜度,它与 HTTP 响应中的 Expires 头不是同一个东西。传输层返回二百,不会让已经过期的联系信息重新变得有效。地址和政策需要持续维护,并在内容过时之前更新。使用 Canonical、Policy 等可选字段时,也应遵守各自规则,不能把文档里任意一个网址都当成已经验证的可信声明。 坏掉的样子 宽泛的应用回退路由可能对任何未知地址都返回首页或登录页,并统一使用二百状态。浅层健康检查认为路径可达,真正的协议客户端却得不到可用文档。因此要保留准确路由,同时检查媒体类型与内容。意外跳转到另一个源站,还可能改变信任上下文,而不只是移动一个文件。仅检查最终状态成功,可能遗漏这些协议层面的错误。 命名空间本身不会认证内容。HTTPS 校验帮助建立到目标主机的连接,文档验证及协议要求的其他检查,才进一步判断内容是否可用。存在安全联系信息,不等于授权外部人员扫描或攻击服务。部署也不能因为一个标准资源计划公开,就不加区分地暴露整个目录。应围绕实际支持的资源配置路由和权限,而不是把前缀当成自动开放所有内容的规则。 ...

六月 22, 2027 · 2 分钟 · XB Field Notes
文章的关闭 EDNS 实验中,UDP 返回 462 字节,设置 TC,只含四条 TXT 中的两条;可用 TCP 路径重试返回 892 字节,TC 清除,包含全部四条。

UDP 回答被截断时,DNS 需要可工作的 TCP 路径

DNS 同时支持 UDP 和 TCP。服务器因可用消息空间而截断响应时,会设置 TC 标志。客户端收到该标志,需要处理结果不完整的情况,通常通过 TCP 重新查询。这涉及当前使用的 DNS 响应限制,并不直接证明物理网络无法承载任何更大的 UDP 数据报。排障时应先确认协议层发生了什么,再讨论传输路径的实际容量。 即使包含记录,TC 仍说明回答不完整。可用 TCP 路径能取回完整集合,只提供 UDP 的端点则无法完成。文章还展示消息边界与连接复用;这不表示 DNS 已加密。 坏掉的样子 允许 UDP 五十三端口却阻止 TCP 五十三端口的网络,可能小查询正常,大回答失败。DNSSEC 数据和较大记录集容易暴露这种差异,但 TCP 并不只用于这些特殊情况。通用 DNS 实现需要支持 TCP,客户端也可以直接选择它。因此,一次小型 UDP 查询成功,不能证明整个 DNS 服务的传输路径完整,尤其不能代替对 TCP 监听和访问规则的检查。 ...

十一月 19, 2026 · 3 分钟 · XB Field Notes
ICMPv6 邻居请求与通告解析本地邻居;邻居发现还包括路由器发现和可达性检测。

IPv6 先用 ICMPv6 解析邻居,TCP 才能正常通信

以太网链路上的 IPv6 主机,需要先取得邻居的链路层目的地址,才能发送相应帧。邻居发现通过 ICMPv6 的邻居请求和邻居通告完成地址解析,同时还涵盖路由器发现、邻居可达性检测等超出 IPv4 ARP 的功能。把它简单理解为“地址更长的 ARP”,会掩盖配置 IPv6 网络时必须考虑的依赖关系,尤其是控制报文与应用通信之间的联系。 坏掉的样子 拥有有效 IPv6 地址和路由,不代表能够解析下一跳的以太网地址。如果必要的请求或通告被过滤,TCP 服务可以一直正常监听,客户端却在交换应用数据前就失败。只开放应用 TCP 端口,无法修复这个较低层依赖。排查时应把邻居表、相关 ICMPv6 流量和服务监听一起观察,不能只从端口是否开放判断整条路径。 缓存状态还可能隐藏错误。已有可用邻居条目的主机,在破坏性规则安装后可能继续通信;新客户端或条目失效之后,故障才暴露。反过来,STALE 状态也不自动意味着损坏:邻居发现可以利用已有链路地址,随后再检查可达性。应理解状态转换,不能要求每个正常条目永远显示 REACHABLE,否则会把正常缓存生命周期误判为故障。 另一类混淆,是在本地以太网段上解析远端应用服务器地址。目的地位于链路之外时,主机通常解析选中的下一跳路由器,而不是远端服务器。判断邻居表应出现哪个地址之前,需要先确认选路。捕获接口错误,或者已有缓存使本次无需重新发送请求,也能解释空白抓包结果,不能据此证明邻居发现根本没有发生。 本图解释 ICMPv6 依赖。邻居解析不认证地址归属,也不单独验证 SLAAC、路由器通告处理或重复地址检测;绘图未重跑网络实验。 快速验证 历史报告使用 Python 3.11.16、iproute2 6.1.0、nftables 1.0.6 与 tcpdump 4.99.3;本轮未重跑。脚本名为 check-neighbor-discovery.py,只应留给另外获得授权的可丢弃 Linux 虚拟机。容器共享其内核,NET_ADMIN、SYS_ADMIN、proc 挂载和命名空间防火墙变更需要这一隔离边界。构建阶段会下载软件包,--network none 只适用于之后的容器运行。保持断言开启,不要为绕过设置失败而放宽宿主安全策略。新建 veth、邻居与防火墙变更仅应存在于实验内部: ...

十一月 16, 2026 · 3 分钟 · XB Field Notes
三个独立数据报卡片分别为 A:1 字节、BC:2 字节、空载荷:0 字节;空载荷仍是一条消息,不代表 EOF。

UDP 保留数据报边界,恢复和时效由应用负责

UDP 在传输端点之间携带独立数据报,八字节首部包含来源端口、目的端口、长度和校验和字段。它不会加入 TCP 的可靠有序字节流或拥塞控制行为,让应用可以选择自己的交付策略,但不承诺所有负载下延迟都更低。应用确实需要某条内容时,丢失的数据报可能比建立连接的开销更昂贵,必须按完整请求过程评估。 原文发送 A、BC 和空数据报,得到长度 1、2、0。八字节 abcdefgh 用三字节缓冲区接收,得到 abc 与 MSG_TRUNC;defgh 被丢弃,下次读取超时,新发的 next 数据报仍能收到。刻意编写的中继把 1、2、3 改成 3、1、1;集合去重只留下 3、1,没有恢复 2 或原始顺序。 原有实验示意,本轮未重跑;中继属于故障注入,不是网络丢包统计或延迟保证。 坏掉的样子 发送成功不证明目标应用已经收到或处理消息。UDP 没有内建确认、重传、去重或顺序保证,因此超时不能告诉支付客户端操作是否发生。允许重试时,应用需要能够处理重复请求和结果不确定的协议,不能把没有响应直接解释成业务失败。消息是否到达、动作是否执行、结果是否持久化,也应具有不同含义。 ...

十一月 11, 2026 · 1 分钟 · XB Field Notes
客户端宣告 4096 字节,但文章服务器把 UDP 限制为 1232。截断回答可能需要 TCP;实际交付仍依赖路径。

EDNS 宣告 UDP 容量,不保证沿途网络交付

EDNS 通过 OPT 伪记录扩展 DNS,其中一项作用是让请求发送方宣告自己准备接收的 UDP 载荷大小,从而允许回答超过传统五百一十二字节 DNS 限制。这是 DNS 交换中的容量宣告,不是网络路径 MTU 发现协议,也不承诺所有中间设备都会交付相同大小的数据报。理解这一区别,才能准确解释大回答为何仍可能失败。 图中数值来自文章明确的回环配置与记录的 DNS 消息长度,不是统一默认值或路径 MTU 测量。OPT 宣告容量,客户端仍须理解 TC 并保留 TCP 访问。 坏掉的样子 客户端宣告四千零九十六字节,不会强迫服务器发送同样大小的响应。服务器可以实施更小的发送上限;需要的回答放不下时,可以返回 TC,客户端仍需可工作的 TCP 路径。增加客户端数字,不能修复服务器有意采用较小预算的策略。应查看两端实际配置与返回内容,而不是把请求里出现的大数值当成双方已承诺一定使用的大小。 ...

十月 28, 2026 · 3 分钟 · XB Field Notes
同一 TCP 连接上,慢处理函数等待时快 HTTP/2 响应完成;缺失 TCP 字节范围阻塞两个流的后续字节。

HTTP/2 消除 HTTP 响应顺序阻塞

这里的“消除队头阻塞”,指解除同一条连接上的 HTTP 响应顺序约束,而不是消除 TCP 队头阻塞。较晚发起的响应可以在较早的处理函数仍然等待时完成,但共享的传输容量、连接级流控和应用瓶颈,依然可能把不同流的进度关联起来。先说明这一层边界,才能区分应用协议的顺序约束与底层传输等待。 分清两种排队 HTTP/1.1 本来就支持持久连接,并非一个请求必须新建一条连接。多个请求可以顺序复用套接字;流水线还允许同时存在多个未完成请求,服务器也可以并行处理安全方法,但同一条连接上的响应必须按照请求顺序发送。因此,第一份响应过慢,会延迟后续响应的交付。客户端经常通过多开连接缓解这个限制。 HTTP/2 给帧关联流标识,在同一条连接上交错传递消息。服务器可以发送已经准备好的响应,不必先完成另一份无关响应。这是 HTTP 层的独立推进能力,不代表每个处理函数获得独占处理器、数据库、带宽或无限调度机会,也不意味着应用的共享资源自动变成互不影响。 TCP 仍然提供一个有序字节流。某段字节缺失时,后面已收到的字节可能必须等待补齐,才能交付应用,其中也可能包含其他 HTTP/2 流的帧;此前已交付字节不会被收回。HTTP/3 使用 QUIC,每条流内仍保持有序,但没有跨流的统一传输交付顺序。共享拥塞与流控预算、应用依赖和 QPACK 头部依赖仍可能产生等待。这是另一协议的规范背景,不是本例执行了 HTTP/3 实验。 并发仍然受到预算约束 对端通过 SETTINGS_MAX_CONCURRENT_STREAMS 限制本方向向它发起的流。非空 DATA 载荷同时消耗流级和连接级额度,计入填充,不计帧头。连接额度耗尽可能阻塞其他流的 DATA;但携带 END_STREAM 的空 DATA 帧仍可在零额度时发送,控制帧不消耗这些窗口。接收方消费正文、更新窗口,以及共享工作池、锁和上游连接的情况,仍会影响实际进度。 应检查所测那一跳真正协商出的协议。浏览器到代理使用 HTTP/2,不能证明代理到源站也是如此;TLS 握手成功也不足以证明使用了 HTTP/2。Go 配置自定义 TLS 参数后,可能需要 ForceAttemptHTTP2 才会尝试 HTTP/2,但最终证据仍是响应协议和协商出的 h2,而不是配置字段已经存在。 ...

九月 15, 2026 · 3 分钟 · XB Field Notes