你在为一个全球 API 平台设计接入层:5000 万用户散布五大洲,移动端占七成,网络丢包率常态 1–3%;API 请求 p99 延迟目标 < 150ms,其中连接建立(DNS + TCP + TLS)往往就吃掉移动端首字节时间的一半。一个从东京发往美西机房的请求,光物理往返(RTT)就 ~120ms,握手若要 3 个 RTT 就是 360ms——用户还没看到一个字节。
问题不在「加机器」,而在每一个 RTT 都要抠:DNS 怎么把用户导到最近的健康机房、传输层用 TCP 还是 UDP、握手怎么从 3 个 RTT 压到 0、负载均衡在第 4 层还是第 7 层做。这四件事决定了首包延迟,也是架构师面试里区分「会调 Nginx」和「懂网络」的分水岭。
graph LR
U["客户端
移动/浏览器"]
DNS["DNS 解析
GeoDNS + Anycast"]
EDGE["Edge / L4 LB
Anycast VIP · DSR"]
L7["L7 LB / 网关
TLS 终止 · 路由"]
APP["App 集群"]
U -->|"① 域名→IP"| DNS
U -->|"② TCP/QUIC 握手"| EDGE
EDGE -->|"③ TLS 握手 + HTTP"| L7
L7 -->|"④ keep-alive 连接池"| APP
classDef client fill:#1a2530,stroke:#64c8ff,color:#e8eef5
classDef edge fill:#0e2030,stroke:#5eead4,color:#e8eef5
classDef app fill:#2a1530,stroke:#ff7ab6,color:#e8eef5
class U client
class DNS,EDGE,L7 edge
class APP app
DNS 决定去哪个机房;L4 决定进哪台边缘;L7 终止 TLS 并按路径/租户路由;到源站复用长连接省握手
四个组件各管一段延迟:DNS 把用户就近导流(Anycast 让「最近」由 BGP 路由自动决定);L4 LB 按连接四元组转发、吞吐极高但不看内容;L7 LB/网关 终止 TLS、看到 HTTP 报文才能做路径路由、限流、灰度;到源站用连接池 keep-alive 摊掉握手。理解这条链,才能定位「慢在哪个 RTT」。
原理:TCP 提供可靠、有序、面向连接的字节流——三次握手建连、序列号 + ACK 重传丢包、滑动窗口做流控、拥塞控制(Cubic/BBR)避免压垮网络。代价是:建连要 1 个 RTT,且字节流的「有序」是把双刃剑——一个包丢了,后面已到达的字节也必须在内核缓冲区里干等重传,这就是队头阻塞(HOL blocking)的根源。UDP 则是无连接的数据报:发出去不管到没到、不排序、无拥塞控制,零握手、零队头阻塞,但可靠性/有序/拥塞全得应用层自己造。
# 选型直觉:先问「丢一个包,业务能不能接受?」
可丢、要最新 → UDP(实时语音:丢帧不如等旧帧)
不可丢、要有序 → TCP / QUIC(API、文件、支付)
要有序但怕队头阻塞 + 移动端频繁切网 → QUIC(HTTP/3)
原理:HTTP 性能史,本质是与队头阻塞的三次战争。HTTP/1.1 一条连接一次只能处理一个请求(pipelining 因实现问题几乎废弃),浏览器只好开 6 条 TCP 并发——这是连接级队头阻塞。HTTP/2 在一条 TCP 上做应用层多路复用:把请求切成带 stream id 的 frame 交错传输,解决了应用层队头阻塞——但所有 stream 仍共享一条 TCP 字节流,一个 TCP 包丢失会卡住全部并发 stream(传输层队头阻塞仍在,且比 HTTP/1.1 更痛,因为 6 条连接的损失被压进 1 条)。HTTP/3 干脆抛弃 TCP,在 QUIC 上跑:每条 stream 有独立的序列空间,丢包只卡它自己那条流,其余照常交付——队头阻塞被彻底解决。
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| 传输层 | TCP | TCP | QUIC / UDP |
| 并发 | 多条连接 | 单连接多路复用 | 单连接多路复用 |
| 队头阻塞 | 连接级(严重) | TCP 传输层仍在 | 无(每流独立) |
| 建连 RTT | TCP+TLS 2–3 | TCP+TLS 2–3 | 1(含 TLS)/ 0-RTT |
| 头部 | 明文重复 | HPACK 压缩 | QPACK 压缩 |
| 换网重连 | 断 | 断 | 连接迁移不断 |
关键洞察:HTTP/2 在丢包高的移动/跨洋链路上,性能可能不如 HTTP/1.1 的多连接——因为把所有蛋放进一条 TCP 篮子,篮子一晃全撒。这正是必须下沉到 QUIC 的原因:队头阻塞不在应用层,光升级 HTTP 版本治不了根,得换传输层。
原理:TLS 在明文 TCP 上叠加加密。TLS 1.2 需要 2 个 RTT(ClientHello→ServerHello+证书→密钥交换→Finished)才能发第一个字节,叠加 TCP 的 1-RTT 就是 3 个 RTT。TLS 1.3 砍掉一轮协商,只要 1 个 RTT;对回访用户用会话票据(session ticket)可做 0-RTT:客户端在第一个包里就带上应用数据(early data),握手与数据并行。QUIC 更进一步,把传输握手和 TLS 握手合并,新连接 1-RTT、回访 0-RTT。省下的每个 RTT 在跨洋链路上就是几十上百毫秒。
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: TLS 1.2 (2-RTT)
C->>S: ClientHello
S->>C: ServerHello + Cert
C->>S: KeyExchange + Finished
S->>C: Finished ✓ 才能发数据
Note over C,S: TLS 1.3 0-RTT (回访)
C->>S: ClientHello + early data (请求已在此)
S->>C: 响应 + Finished
POST /transfer 这类非幂等写——否则一次转账可能被重放成两次。工程上:浏览器只在 0-RTT 里发 GET;服务端对非幂等请求强制回退到完整 1-RTT 握手。
原理:请求发出前先要 DNS 解析——递归解析器逐级问 root→TLD→权威服务器,靠 TTL 缓存避免每次都走全程。全球化用两招:GeoDNS 按解析者地理位置返回不同 IP;Anycast 让同一个 IP 在多地宣告,BGP 自动把用户路由到网络最近的节点。流量到了机房,负载均衡分两层:L4(传输层)只看 IP:Port 四元组做连接转发,不解密、吞吐极高、常配 DSR(回包绕过 LB 直返);L7(应用层)终止 TLS、解析 HTTP,才能按 URL 路径/Header/Cookie 路由、做金丝雀、限流、WAF——代价是每连接都要解密解析,成本高得多。
| L4 (传输层) | L7 (应用层) | |
|---|---|---|
| 看到什么 | IP:Port 四元组 | 完整 HTTP(URL/Header/Cookie) |
| 能做 | 连接级转发、DSR | 路径路由、灰度、限流、TLS 终止 |
| 吞吐/成本 | 极高 / 低 | 较低 / 高(要解密解析) |
| 典型 | LVS、AWS NLB、Maglev | Nginx、Envoy、AWS ALB |
# 生产常见组合:Anycast → L4(抗量+均衡)→ L7(智能路由)
Client --DNS/Anycast--> [L4 LB: 一致性哈希选边缘, DSR 回程]
--> [L7 网关: 终止 TLS, /v2/* 灰度 5%]
--> App 连接池 (keep-alive 复用, 免重复握手)
# L4 用一致性哈希是为了后端增减时不打乱既有连接(见 Day 4 分片)
高频追问:
HTTP/2 冷启动:DNS 解析(本地缓存命中≈0,否则 1 RTT)+ TCP 三次握手 1 RTT + TLS 1.3 握手 1 RTT,才发出第一个请求,再等 1 RTT 拿响应。不含 DNS 约 3 个 RTT ≈ 360ms;DNS 未命中再加 ~1 RTT。
HTTP/3 首次:QUIC 把传输握手和 TLS 合并成 1 RTT,加取响应 1 RTT ≈ 2 RTT ≈ 240ms,省掉一整个 RTT。
HTTP/3 回访 0-RTT:请求随第一个包发出(early data),响应 1 RTT 回来 ≈ 120ms——从 360ms 到 120ms,跨洋链路上就是「转圈」和「秒开」的差别。
这也解释了为什么大厂死磕握手 RTT:物理光速不可谈判,唯一能省的就是来回次数。
HTTP/1.1 时代浏览器开 6 条 TCP,一条卡了另外 5 条照跑——损失被分散。HTTP/2 把所有请求压进一条 TCP,应用层看似并发,但底层字节流一旦丢包,TCP 必须按序交付,全部 stream 一起等重传。低丢包链路上多路复用净赚;高丢包链路上把鸡蛋全放一个篮子反而更脆。
判断:看目标用户网络质量。数据中心内部/低丢包 → HTTP/2 收益明确;大量移动弱网/跨洋 → 要么保留多连接,要么直接上 HTTP/3(QUIC 每流独立,从根上免疫)。别只看「版本号更高」。
风险:Anycast 的「最近」由 BGP 路由决定,路由随网络状况抖动。若一条 TCP 长连接中途 BGP 收敛把后续包导向另一个节点,那个节点没有这条连接的状态(seq、TLS 会话),连接直接 RST 断掉。所以 Anycast 传统上更适合短、无状态的交互(DNS 一问一答)。
QUIC 为何更配:QUIC 用连接 ID而非四元组标识连接,且支持连接迁移——即便包被路由到新节点、或客户端换了 IP/端口,凭连接 ID 仍能认领这条连接、无需重握手。这让 Anycast + 长连接第一次真正可行,也是移动端换 WiFi/蜂窝不断连的底层原因。
终止 TLS 的代价:每条连接都要在 L7 解密,CPU 成本高、单机吞吐远低于 L4;且明文报文在 LB 内存中出现,成为攻击面(密钥、请求内容集中)。收益是能按 URL/Header 路由、灰度、限流、WAF——这些不解密根本做不了。
为何两层:L4 扛量、L7 扛智能。L4(Maglev/LVS/NLB)用一致性哈希把海量连接均匀撒给一组 L7,自己不解密、吞吐极高、还能配 DSR 让回包绕过 LB 直返客户端(省一半带宽);L7 只需处理分到自己的那份连接做精细路由。纯 L7 顶在最前面既贵又难水平扩展,还把 DDoS 直接引到解密层。分层等于「先粗筛再精调」。
贯穿主线:物理光速定死了单个 RTT,架构能做的只有「减少来回次数」和「把来回摊薄」,而每一次激进省 RTT(0-RTT/TFO)几乎都在拿安全或一致性换延迟。