计算机网络 32:请求到达哪个后端,四层/七层负载均衡、反向代理与健康检查
后端 A 被摘除后,为什么新请求去了 B,一条已经连到 A 的长连接却还能继续得到 A 的响应?“后端健康”若没有同时说明选择单位、检查时刻和已有状态,只是一个容易误导的标签。 第 21–24 篇分别给出了名字解析、HTTP 与 TLS,第 31 篇解释五元组和有状态转发。本篇把入口之后的选择拆成四层负载均衡、七层反向代理和健康判定,并用本机连接实验观察后端关闭监听前后的差别。 四层选择的是连接目标 四层负载均衡依据 IP、端口和传输协议等信息选择后端。实现可以转发报文、改写地址,也可以终止客户端连接后再建立上游连接;“四层”描述决策主要不依赖 HTTP 路径、方法或字段,不保证实现内部一定只有一条连接。 一次 TCP 连接选中后端 A 后,后续字节通常沿已有映射或上游连接继续发送。轮询算法按连接选择时,一个包含许多请求的长连接只贡献一次选择;因此“连接数平均”不等于“请求数平均”,更不等于后端工作量平均。 关闭 A 的监听 socket 只阻止新连接进入。操作系统中已经建立的连接是独立对象,只要进程继续服务并且连接未被关闭,已有客户端仍可交换数据。若进程退出、连接被重置或负载均...
计算机网络 31:NAT 与防火墙改变了什么,五元组、连接跟踪、SNAT/DNAT 与超时
客户端连接 203.0.113.9:443,服务端抓包看到的来源却不是客户端地址;访问网关的 8443 端口,内部服务收到的目标又变成 10.0.0.10:443。如果中途超时,返回包可能再也找不到原来的内部端点。地址转换、状态跟踪和过滤共同出现在一台网关上,但它们解决的是三件不同的事。 第 07 篇给出了路由查找,第 16 篇讨论 TCP 连接状态,第 30 篇区分主机里的观察点。本篇只讨论传统 NAT 与有状态防火墙怎样改变一次转发的可见五元组和处理结果。NAT 不等于防火墙,conntrack 也不等于 TCP 自己的状态机。 五元组必须带方向读取 一条常见传输流用五元组表示: {协议, 源地址, 源端口, 目标地址, 目标端口} 协议字段不能省略,因为 TCP 与 UDP 可以使用相同的四个地址和端口而仍是不同的流。方向也不能省略:返回方向会交换源与目标,经过 NAT 后还会出现转换前后的两个版本。 假设内部客户端 10.0.0.2:49152 访问 203.0.113.9:443,网关把来源改为 198.51.100.7:62001。内部侧看到: T...
计算机网络 30:数据如何经过 Linux 主机,socket、协议栈、队列、软中断与网卡卸载
应用调用 sendall() 返回时,数据可能仍在本机发送队列中;抓包显示一个大于 MTU 的 TCP 对象时,线上也未必出现过同样大小的帧;监控里的网卡计数增长,则不一定只来自当前进程。三种观察都是真的,但它们观察的是 Linux 网络路径上的不同位置。 第 03 篇已经说明抓包点决定证据边界,第 19 篇区分了发送端、接收端与路径上的速度限制,第 29 篇讨论了应用怎样等待 socket 就绪。本篇把这些观察点放回一台 Linux 主机:应用通过 socket 提交或接收字节,内核协议栈维护连接和队列,设备收发路径可能经过 NAPI、软中断与卸载。每个观察点只能证明其附近发生了什么。 socket 是用户态与协议栈之间的接口 Linux socket(7) 把 socket 描述为用户进程与内核协议栈之间的统一接口。文件描述符只是应用持有的引用;TCP 连接状态、发送和接收缓冲、重传及拥塞控制由内核维护。一次系统调用返回,不等于整条端到端路径完成。 以一次 TCP 发送为例,可以分出四个不同事件: send() 或 sendall() 把字节交给本机内核,或者因错误而失败;...
计算机网络 29:一个进程怎样服务很多连接,阻塞、非阻塞、就绪通知与超时
第 02 篇用长度前缀恢复消息边界,第 16 篇区分连接与半关闭,第 18 篇说明接收方变慢会形成背压。这些机制在单个连接上成立,但服务器还要回答另一个问题:一个连接停在 recv(),其他连接是否仍有机会被处理? 阻塞、非阻塞和就绪通知不是三种协议。它们是应用安排等待的方式。阻塞线程可以把一个连接的顺序逻辑写得很直接;事件循环把多个连接的等待集中起来,只在内核报告“当前可能推进”时执行少量工作。两者都必须正确处理短读写、EOF、背压和超时,也都不能仅凭连接数推导吞吐。 本篇用同一个四字节长度前缀回显协议比较两种本机实现:阻塞模型为每条连接分配一个工作线程;事件循环使用 Python selectors.DefaultSelector 和非阻塞 socket。实验只验证有限的并发正确性,不做容量或性能结论。 阻塞描述等待发生在哪里 阻塞 socket 默认允许 accept()、recv() 或 send() 暂停当前线程,直到操作能够推进、发生错误或命中超时。暂停的是调用线程,不是“整个进程必然停止”。如果每条连接有独立工作线程,一个慢客户端阻塞其中一个线程时,其他线程仍能处理...
计算机网络 28:HTTP/3 怎样运行在 QUIC 上,映射、QPACK、协商与回退
第 26 篇已经说明,HTTP/2 把请求拆成流,却仍受同一条 TCP 字节流的有序交付约束。第 27 篇进一步拆开了 QUIC 的包号、流偏移、流量控制和共享拥塞控制。HTTP/3 站在这套传输能力之上,但它并不等于“把 HTTP/2 帧原样塞进 QUIC”。请求怎样占用流、连接级状态放在哪里、字段压缩如何同步、客户端何时回退,都需要新的映射规则。 本篇回答一个有限问题:HTTP 语义怎样落到 QUIC 流上,以及一次等待究竟来自请求流缺口、QPACK 依赖、流量控制还是共享拥塞窗口。验证采用 RFC 核查和标准库手算。当前环境的 curl 8.5.0 只列出 HTTP/2,没有 HTTP/3 功能;没有运行成熟 HTTP/3 客户端与服务端,也不提交协议性能排名。 HTTP 语义没有因传输层改变 HTTP/3 仍使用 HTTP 的方法、状态码和字段语义。变化发生在消息的分帧与承载层:HTTP/2 在一条 TCP 字节流内维护自己的 stream id;HTTP/3 直接使用 QUIC stream 作为请求并发的基本单位。 一条客户端发起的双向 QUIC 请求流只承载一项请求...
计算机网络 27:QUIC 为什么重新实现传输机制,包号、流、恢复、拥塞与迁移
HTTP/2 已经把一个 TCP 连接里的 HTTP 消息拆成多条流。它解决了 HTTP/1.1 响应必须排队返回的问题,却没有解决 TCP 字节流自己的等待:一个 TCP 段丢失后,内核不能把后面的字节先交给上层,即使那些字节只属于另一条 HTTP/2 流。应用层分帧已经知道“这几个字节属于流 3”,TCP 仍然只看到“前面的字节没齐”。 QUIC 的入口问题就在这里:如果应用已经需要多流、加密、恢复、拥塞控制和连接迁移,为什么还要把这些能力放在内核 TCP 之上,再在上层补一套绕不过去的状态?QUIC 的选择是把传输机制搬到 UDP 之上,由端点实现包号、流、恢复、拥塞控制、TLS 集成和路径变化处理。许多实现选择用户态,这是部署方式,不是 QUIC 规范要求。UDP 在这里不是可靠传输,只是报文承载;它也可能被网络策略阻断。 前置是第 17 篇超时与重传、第 18 篇流量控制、第 19 篇拥塞控制、第 24 篇 TLS 身份验证和第 25 篇会话恢复。本篇只讨论 QUIC 传输机制。HTTP/3 怎样把请求、响应、控制流和 QPACK 映射到 QUIC,放到下一篇。文中的验...
分布式系统(26):Spanner 的时间区间、提交等待与一致快照
一笔转账同时修改两个分片。两边最终都提交,只解决了共同终局;报表还需要在一次读取中看到完整的转账结果。如果转账已经向客户端报告成功,随后发起的强一致读取也不能因为选择了较慢的时钟而退回转账之前。 分布式系统(25):分布式事务的决定、补偿与消息边界 Spanner 把这些要求组合在同一个数据库里:每个复制组保存可恢复的状态,跨组事务决定共同结果,并发控制约束冲突操作,时间戳和多版本读取决定快照内容。TrueTime 提供带误差边界的时间区间,提交等待把时间戳顺序与真实先后连接起来。机制推导以 OSDI 2012 原论文为准,课程结构对应 MIT 6.5840 和 Stanford CS244b;现行云产品的隔离级别单独说明。 复制、事务与时间各自约束什么 设账户 x 位于组 A,账户 y 位于组 B。一次事务要从 x 扣款并向 y 加款。每个组内部有多个副本,通过 Paxos 复制状态;A、B 作为两个业务参与组,通过两阶段提交协调这笔转账。组内复制多数与跨组全部参与者是两个不同集合:A 的多数派确认,不能代替 B 的准备结果。 flowchart TB T[跨分片转账 T]...
分布式系统(25):分布式事务的决定、补偿与消息边界
订单记录已经提交,支付消息却没有发出。把顺序反过来,支付消息可能已经被处理,订单事务随后回滚。两个系统各自正确地完成一次操作,仍然不能保证这两次操作具有共同的结果。 分布式系统(24):分片与在线迁移的归属边界 前篇解决一个分片迁移后由谁服务。跨分片下单还需要回答另一件事:订单、库存和支付分别位于不同资源时,哪些变化必须一起提交,哪些中间结果允许出现,失败后还剩什么义务。站内既有的分布式事务覆盖更多方案名称;这里沿同一条订单链路推导失败路径,再用本地实验检查原子边界。 两次成功调用之间存在失败窗口 先限定业务含义。订单服务创建的是“待支付订单”,消息是“请求扣款”,消费者在自己的账本里扣款。订单创建成功不代表支付完成。若业务要求两个资源共同提交或共同中止,需要跨资源原子提交;若还要求读者看不到部分结果,还必须设计跨资源的隔离与读规则。若允许订单先进入待处理状态,则可以持久保存后续动作,由工作流继续推进。这是对可观察行为的选择。 sequenceDiagram participant S as 订单服务 participant D as 订单库 participant ...
计算机网络 26:HTTP/2 的多路复用解决了哪一层阻塞,帧、流、HPACK 与窗口
一个慢响应还没结束,同一连接上的小响应能不能先完成?如果能,为什么丢失一个 TCP 段仍可能让多个响应一起等待?这两个问题分别涉及 HTTP 消息的组织方式和底层字节流的交付条件。 前置是第 18 篇接收窗口、第 22 篇消息边界与第 24 篇 TLS。本篇把流标识、字段压缩、接收窗口和 TCP 顺序交付分开,避免把“多路复用”解释成每个请求拥有独立网络通道。 消息拆成帧后,响应可以交错 HTTP/1.1 流水线允许前一个请求尚未收到响应时继续发送请求,但响应仍按请求顺序返回。一个响应占用字节流时,后一个响应不能随意把正文插进去,否则接收者无法按原有消息规则区分归属。 HTTP/2 在帧头中携带流标识。接收端先识别帧,再把内容交给相应的流。一个连接可以同时存在多个未关闭的流,因此服务器能够在一个大响应的两个 DATA 帧之间发送另一个流的响应。请求和响应仍保留 HTTP 语义,改变的是表达方式。 RFC 9113 §4.1规定固定 9 字节帧头,包括长度、类型、标志和流标识。长度描述帧载荷,不包含这 9 字节。流标识 0 用于连接范围的控制,不能当成普通请求编号;客户端发起的流使...
计算机体系结构 16:缓存基础
缓存基础 同一个求和循环里,处理器不断访问相邻数组元素,也会反复取循环体那几条指令。若每一次都从更慢的下层存储取数据,流水线前几篇讨论的前递、停顿和分支恢复很快就被访存等待淹没。缓存的基本承诺很小:把最近用过或附近可能要用的一块数据放在更靠近处理器的位置,下一次地址命中时少走一段路。 本篇的核心问题是:给定一串地址,怎样判断每一次访问落在哪个 set、比较哪个 tag、替换哪一行,以及一次 miss 是冷缺失、容量缺失还是冲突缺失?核心案例使用 16B block、4 个 cache line 的有限模型,对同一地址序列分别跑直接映射和两路组相联。证据等级为功能执行,所有数字来自可复跑 Python 模型;它不是 gem5 仿真,也不是本机硬件计数器。 实验目录为 examples/computer-architecture/cache/。本文附件包括 mapping.json.txt、cache_model.py.txt 和 run_cases.py.txt。附件扩展名使用 .txt,便于博客直接下载查看。 从地址到 block、set 和 tag 缓存不按单个字节管理数据,而...









