--- tags: [计算机网络, CDN, CacheControl, LoadBalancer, L4, L7, Nginx, Go http.Server] create time: 2026-05-18 05:20 --- # CDN、负载均衡与 Go Server 调优 ## 概述 在内核层面做好调优之后,架构层面的优化才是真正拉开差距的地方。本章覆盖 CDN 缓存策略、L4/L7 负载均衡选型,以及 Go http.Server 的极致调优实践。 ## CDN 缓存策略 ### CDN 缓存命中流程 ```mermaid sequenceDiagram participant U as 用户浏览器 participant CDN as CDN Edge Cache participant Origin as 源站服务器 Note over U,Origin: Case 1: Cache HIT ✅ U->>CDN: GET /static/logo.png?v=2 Note over CDN: Cache HIT! Age: 3600 CDN-->>U: 200 OK (Age: 3600, X-Cache: HIT) Note over U,Origin: Case 2: Cache MISS ❌ → 回源 U->>CDN: GET /api/user/profile Note over CDN: Cache MISS (no-cache) CDN->>Origin: GET /api/user/profile Origin-->>CDN: 200 + body + Cache-Control CDN-->>U: 200 OK (X-Cache: MISS) Note over CDN: Set TTL for next request ``` ### Cache-Control 指令速查表 | 指令 | 含义 | CDN 行为 | 适用场景 | |------|------|---------|---------| | `Cache-Control: no-cache` | 每次向源站验证 | 返回 ETag/If-Modified-Since | API 响应(需要新鲜数据但不想每次都全量回源)| | `Cache-Control: no-store` | 不缓存任何内容 | 永远回源 | 敏感数据、个人信息接口 | | `Cache-Control: max-age=3600` | 缓存 1 小时 | 命中期间不回源 | 静态资源、首页 HTML | | `Cache-Control: immutable` | 资源永不改变(版本化 URL)| 浏览器永久缓存 | CSS/JS/图片(带 hash 文件名)| | `Cache-Control: public` | 任何节点都可以缓存 | 边缘节点缓存 | 公开静态资源 | | `Vary: Accept-Encoding` | 按编码方式分别缓存 | gzip/webp/原文件分开存 | 支持多种压缩方式的资源 | ```bash # curl 实战:检查 CDN 缓存状态 $ curl -I https://cdn.example.com/style.css HTTP/2 200 cache-control: public, max-age=86400 age: 1234 # ← 被 CDN 缓存了 1234 秒 x-cache: HIT # ← Cloudflare 命中标记 cf-cache-status: HIT # ← Cloudflare 自己的标记 content-encoding: br # ← Brotli 压缩版 etag: "abc123" # ← 可用于 If-None-Match 验证 $ curl -I https://cdn.example.com/api/data HTTP/2 200 cache-control: no-cache x-cache: MISS # ← 未命中,回源了 ``` ### CDN 刷新策略 ``` 被动失效(自然过期): 由 max-age + 到达时间自动控制 主动刷新 (Purge): API/cURL 立即清除特定 URL 缓存 批量刷新: CDN 提供 API 清除整个目录前缀 # Cloudflare Purge API curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache" \ -H "Authorization: Bearer ${CF_TOKEN}" \ -H "Content-Type: application/json" \ --data '{"files":["https://cdn.example.com/js/app.a1b2c3.js"]}' # 最佳实践: 带版本号的 URL → 发布新版本不需要 purge # 旧: /js/app.js → 更新需 purge # 新: /js/app.a1b2c3.js → 直接新增 URL,旧 URL 仍命中旧缓存 ``` ## Load Balancer —— L4 vs L7 深度对比 ### 选型决策树 ```mermaid flowchart TD Q{"你的协议是什么?"} Q -->|"TCP/UDP (非HTTP)"|"选择 L4 LB" Q -->|"HTTP/gRPC/WebSocket"|"选择 L7 LB" L4["L4 负载均衡
基于 IP+Port 转发
⚡ 更快, 更轻量"] --> Examples["典型产品:
IPVS, HAProxy(L4), AWS NLB,
iptables/ipset"] L7["L7 负载均衡
基于 URL/Header/Cookie 路由
🧠 更智能, 功能丰富"] --> Examples2["典型产品:
Nginx, Envoy, Traefik,
AWS ALB, Kong"] style L4 fill:#DDA0DD,color:#000 style L7 fill:#FFD700,color:#000 ``` ### L4 vs L7 特性对照表 | 维度 | L4 (传输层) | L7 (应用层) | |------|-----------|------------| | OSI 层级 | TCP/UDP | HTTP/gRPC/WebSocket | | 决策依据 | IP + Port | URL Path / Header / Cookie | | TLS 卸载 | ✅(需配证书) | ✅(更常用,且支持 SNI) | | 路由策略 | 轮询/加权/最少连接 | URI-based, header-match, regex | | 健康检查 | TCP connect / UDP echo | HTTP GET, gRPC health check | | 重试/熔断 | ❌ | ✅ (Envoy/Istio) | | 可观测性 | 连接数/流量/错误率 | 请求/响应头/Body/Delay/Jitter | | 适用场景 | 数据库代理、Redis, UDP 流量 | Web API, WebSocket, HTTP microservices | ### Nginx upstream 策略详解 ```nginx # 定义 upstream 池 upstream backend { least_conn; # 最少连接优先(适合不等长请求) # ip_hash; # 基于源 IP 哈希(会话保持) # random two; # 随机选两个中连接少的 server app1:8080 weight=3; # 权重 3:1 server app2:8080; server app3:8080 backup; # 备份服务器(仅当主节点全挂时才用) keepalive 32; # 保持 32 个空闲连接到后端 keepalive_requests 10000; keepalive_timeout 60s; # 健康检查(NGINX Plus 专有, OSS 版可用 ngx_http_upstream_check_module) # max_fails 3 fail_timeout 30s; # 连续 3 次失败,停 30s } server { listen 443 ssl http2; server_name api.example.com; location /api/v1 { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; # 超时配置 proxy_connect_timeout 5s; # 连接后端超时 proxy_send_timeout 10s; # 写入后端超时 proxy_read_timeout 30s; # 读取后端超时 # 缓冲优化 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 16k; } } ``` ## Go http.Server 终极调优 ### 服务端完整配置 ```go srv := &http.Server{ Addr: ":443", Handler: myHandler, // === 超时设置(防慢连接攻击 Slowloris)=== ReadTimeout: 10 * time.Second, // 读完整请求体(含 Body)的时间 ReadHeaderTimeout: 5 * time.Second, // ⭐ 单独控制头部读取超时 WriteTimeout: 30 * time.Second, // 写出响应的总时间 IdleTimeout: 120 * time.Second, // Keep-Alive 空闲多久断开 // === 安全边界 === MaxHeaderBytes: 1 << 20, // 1MB 最大 Header // TLSConfig 继承自 crypto/tls 最佳实践 TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS13, NextProtos: []string{"h2", "http/1.1"}, PreferServerCipherSuites: true, }, } // 优雅停机 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() srv.Shutdown(ctx) // 停止接受新连接,等待已有连接处理完 ``` ### 出站请求 Transport 调优 ```go // 为 http.Client 配置高性能 Transport outboundTransport := &http.Transport{ // 连接池大小 MaxIdleConns: 200, MaxIdleConnsPerHost: 50, // Go 默认值是 2,这是最大的坑之一! IdleConnTimeout: 90 * time.Second, // 超时控制 TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 30 * time.Second, ExpectContinueTimeout: 1 * time.Second, // 是否复用 DisableKeepAlives: false, // 必须开启! DisableCompression: true, // 自行压缩更安全(防 BREACH 攻击) // DialContext 自定义(可选:带 DNS 预解析的 dialer) DialContext: (&net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, } outboundClient := &http.Client{ Transport: outboundTransport, Timeout: 45 * time.Second, // 顶层总超时 } ``` ```go // 🐛 经典 Bug: MaxIdleConnsPerHost 默认只有 2! // 这意味着你访问同一个后端时,最多只有 2 个连接可以复用 // 高并发场景下会频繁创建新连接,浪费 TIME_WAIT // // 修复: 显式设置为合理的值(根据 QPS 和连接池大小反推) transport := &http.Transport{ MaxIdleConnsPerHost: 50, // ← 这行不能省! } ``` ### 优雅停机模式 ```go func gracefulShutdown(srv *http.Server, sig os.Signal) { // 等待 SIGINT/SIGTERM <-sig log.Println("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Printf("server forced to shutdown: %v", err) } log.Println("server exited") } // systemd 或 k8s 都会发送 SIGTERM // gracefulShutdown 确保正在处理的请求完成后再退出 ``` ## 关联笔记 - [[hhs/NETWORK/TCP内核调优与连接复用]] — 内核参数与 keep-alive 的全栈配置 - [[hhs/NETWORK/HTTP-1.1完全指南]] — HTTP 协议的细节基础 - [[hhs/NETWORK/HTTP/2多路复用]] — HTTP/2 对负载均衡的影响 - [[hhs/NETWORK/TCP段结构与状态机]] — 分析 TIME_WAIT / CLOSE_WAIT 问题