跳到内容

14.3 HTTP、缓存、CDN 与容量优化

网络路径正常,用户仍可能慢:缓存键不稳定导致每次回源,响应不可压缩,负载均衡把流量送进过载池,或重试把一次故障放大成拥塞。应用与边缘优化必须围绕用户指标和系统容量共同设计。

先定位首字节时间

客户端的 TTFB 大致包含:

text
request upload
+ network to edge/server
+ edge queue and processing
+ origin/upstream queue and processing
+ network back to client
+ first response bytes

因此 TTFB 高不能自动判为“服务端代码慢”。用同一 trace ID 对齐这些时间:

位置应记录
client/RUMDNS、connect、TLS、TTFB、protocol、network type
CDN/LBcache status、edge time、origin time、selected backend
applicationqueue time、handler time、status、request size
database/upstreampool wait、RPC time、attempt count、timeout

若应用 handler 只有 20 ms,而负载均衡排队 800 ms,继续优化 SQL 不会改善该样本。

DNS:减少依赖,不是盲目增加 TTL

DNS 性能受 resolver 距离与缓存、CNAME chain、权威服务可用性、DNSSEC 和响应大小影响。浏览器页面引用很多资源,也不等于产生同样数量的 DNS 查询;同一 hostname 可共享缓存和连接。

优化原则:

  • 减少没有架构价值的独立 hostname 与 CNAME 层级;
  • 使用可靠、低延迟且有冗余的权威 DNS;
  • TTL 在缓存效率与变更速度之间取舍;
  • 变更前先降低 TTL,等待旧 TTL 覆盖后再切换;
  • 监控 NOERROR 空答案、NXDOMAINSERVFAIL、truncation 与 DNSSEC;
  • 同时发布 A/AAAA 时,分别验证两条路径。

dns-prefetch 只预解析名称;preconnect 可能提前完成 DNS 与连接/TLS。它们会消耗 socket、CPU 和服务端资源,只应用于用户很可能访问的少量关键 origins。

预连接只优化该 origin 的连接准备,不能把“10 个资源”简单乘以一遍 DNS+TCP 时间,因为浏览器会缓存解析并复用/多路复用连接。

HTTP 缓存:先写对象语义

确定四件事:

  1. 哪些 cache 可以存:shared cache 还是 private browser cache;
  2. cache key 包含什么:URL、method、选定 request headers、租户或认证维度;
  3. 多久 fresh;
  4. stale 后如何 revalidate,以及源站失败时是否允许服务 stale。

内容寻址的静态资源

http
GET /assets/app.7f3a9c1.js HTTP/1.1

HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
Content-Type: text/javascript

只有文件名随内容变化时,长 freshness 才安全。若同一 URL 的内容会被覆盖,immutable 会让修复难以及时到达客户端。

需要重验证的文档

http
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "revision-42"

no-cache 允许存储,但复用前通常必须 revalidate;no-store 才是要求 cache 不存储。客户端后续可发送:

http
If-None-Match: "revision-42"

若表示未变化,服务端返回 304,不发送完整 representation。304 仍有 headers 和网络往返,不能称为“零字节、零成本”。

Vary 与个性化内容

内容若随 Accept-Encoding、语言等请求头变化,响应应正确设置 Vary。但把高基数或用户特有 header 放入 cache key 会导致命中率崩溃。

带认证或个性化的响应不能因为“想提高命中率”就标记为 public。共享缓存中的跨用户数据泄漏比性能回退严重得多。

压缩:按内容和 CPU 取舍

文本型 HTML、CSS、JavaScript、JSON 通常适合 gzip 或 Brotli;已经压缩的 JPEG、PNG、视频、ZIP 再压缩收益很小。动态高等级压缩可能增加 CPU 与 TTFB。

bash
curl --compressed --silent --show-error --dump-header - \
  --output /dev/null https://example.com/app.js

检查 Content-EncodingVary: Accept-Encoding、原始大小、传输大小与服务端 CPU。预压缩静态资源可以用构建时间换请求时间,但要确保内容类型和缓存变体正确。

HTTP 连接与请求形态

  • HTTP/1.1:控制每 origin 并发连接,尽量复用,避免大量短连接;
  • HTTP/2:利用一条连接多路复用,谨慎处理优先级、单连接故障域与过大的并发 streams;
  • HTTP/3:验证 UDP/QUIC 可达性、fallback、QPACK 与连接迁移观测;
  • 所有版本:设置 request/response deadline、body 上限和 backpressure。

“把所有小文件合成一个大文件”不是跨协议通用答案。HTTP/2/3 降低了多请求的连接开销,但每个请求仍有 headers、调度和服务端成本;过度打包又会扩大缓存失效范围。根据依赖图、缓存粒度与实际 waterfall 决策。

CDN:优化 hit 之前先定义 cache key

一次 CDN 请求可能是:

  • hit:边缘直接服务;
  • miss:向 origin 拉取;
  • revalidated:边缘条件请求验证后继续使用;
  • stale:按策略在源站异常时服务旧对象;
  • bypass:因规则、认证或请求特征跳过缓存。

重点指标:

text
request hit ratio
byte hit ratio
origin requests and egress
edge TTFB by region
miss/revalidation latency
cache-key cardinality
purge propagation and errors

高 request hit ratio 不一定节省最多带宽;大量小对象命中而大对象回源时,byte hit ratio 可能仍很低。

常见问题:Cookie 或 query 参数无意进入 cache key、不同编码/语言没有正确 Vary、错误响应被缓存、所有 edge 同时回源造成 thundering herd。可用 request coalescing、origin shield、stale-if-error 和带 jitter 的刷新缓解,但必须定义一致性与故障边界。

不要靠猜测 X-Cache 等私有 header 判断所有 CDN。每家产品的 header、日志和命中状态语义不同,应以实际供应商文档与配置为准。

负载均衡与容量

负载均衡无法创造后端容量。若每个实例都已饱和,继续均匀分配只会让全部实例一起排队。

需要共同观察:

  • active connections/requests;
  • queue depth 与 queue time;
  • success/error/timeout rate;
  • per-backend latency 与 saturation;
  • health check 状态变化;
  • retry attempts 与 retry budget;
  • connection pool、ephemeral ports 与 NAT/LB state capacity。

算法与业务成本

round robin 适合后端能力和请求成本相近的场景;least connections 对长连接有帮助,但连接数不一定代表实际工作量;consistent hashing 有利于 affinity/cache locality,却会在成员变化时产生再平衡。

健康检查应验证足以接流量的依赖,又不能因为一个非关键依赖抖动让整个池同时摘除。liveness、readiness 与深度业务探测承担不同职责。

重试会放大负载

一次调用链若每层都允许 3 次尝试,最坏请求量会按层放大。重试只适合可安全重试的操作,并应具备:

  • bounded attempts;
  • exponential backoff 与 jitter;
  • end-to-end deadline;
  • retry budget;
  • idempotency key 或明确幂等语义;
  • 熔断/限流与过载保护。

当服务已过载时,无节制重试会把局部故障变成全局拥塞。

大对象传输

大文件性能同时受 CDN、range requests、传输窗口、带宽和客户端读速限制。检查:

  • 是否支持并正确处理 Range/206;
  • 内容是否适合边缘缓存;
  • 是否需要分块上传、断点续传和 checksum;
  • 应用是否把整个对象读入内存;
  • 限速和公平性是否避免单用户占满链路;
  • 下载测试是否把写盘速度混入网络测量。

/dev/null 测量可以减少本地磁盘影响,但仍包含客户端 CPU、TLS 与内核处理。

从基线到复验

  1. 定义用户 SLI:成功率、p50/p95/p99、吞吐与业务结果;
  2. 按 region、网络、协议、cache status、backend 等维度切分;
  3. 用 trace/抓包/连接状态定位限制阶段;
  4. 形成一个能被证伪的假设;
  5. 在代表性环境只改变一个主要变量;
  6. 同时观察目标指标与 guardrails;
  7. 灰度上线,保留回滚;
  8. 记录结果,包括没有收益的实验。

验收清单

  • [ ] 不用固定毫秒阈值替代业务 SLO;
  • [ ] 能解释 TTFB 为什么不等于应用处理时间;
  • [ ] 能区分 no-cacheno-store 与 immutable;
  • [ ] 能为 CDN 写出 cache key 和 purge 策略;
  • [ ] 能说明负载均衡为何不能解决总容量不足;
  • [ ] 性能变更具备基线、对照、guardrail 与回滚;
  • [ ] IPv4、IPv6、HTTP/2 与 HTTP/3 的关键路径分别验证。

第四卷专业内容总结

这一卷从数据如何分层封装开始,走过 Socket、TCP、HTTP、TLS、WebSocket/gRPC、DNS/CDN/LB、Ethernet、IP、路由、拥塞控制、QUIC、网络诊断与 IPv6,最后落到性能测量和容量验证。

专业判断的共同方法始终一样:明确协议层和状态机,区分规范语义与实现策略,用观测证据限制结论,再通过可重复实验验证。后续统一文风时,故事和比喻会帮助读者进入这些模型,但不会替代机制本身。

规范入口

  • RFC 9110:HTTP Semantics;
  • RFC 9111:HTTP Caching;
  • RFC 9211:Cache-Status;
  • RFC 5861:stale-while-revalidate / stale-if-error;
  • RFC 6585:Additional HTTP Status Codes;
  • RFC 9113、RFC 9114:HTTP/2、HTTP/3。

Built with VitePress | Software Systems Atlas