Files
cs-note/hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优.md
T
2026-05-24 11:42:38 +08:00

8.9 KiB
Raw Blame History

tags, create time
tags create time
计算机网络
CDN
CacheControl
LoadBalancer
L4
L7
Nginx
Go http.Server
2026-05-18 05:20

CDN、负载均衡与 Go Server 调优

概述

在内核层面做好调优之后,架构层面的优化才是真正拉开差距的地方。本章覆盖 CDN 缓存策略、L4/L7 负载均衡选型,以及 Go http.Server 的极致调优实践。

CDN 缓存策略

CDN 缓存命中流程

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/原文件分开存 支持多种压缩方式的资源
# 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 深度对比

选型决策树

flowchart TD
    Q{"你的协议是什么?"}
    
    Q -->|"TCP/UDP (非HTTP)"|"选择 L4 LB"
    Q -->|"HTTP/gRPC/WebSocket"|"选择 L7 LB"
    
    L4["L4 负载均衡<br/>基于 IP+Port 转发<br/>⚡ 更快, 更轻量"] --> Examples["典型产品:<br/>IPVS, HAProxy(L4), AWS NLB,<br/>iptables/ipset"]
    
    L7["L7 负载均衡<br/>基于 URL/Header/Cookie 路由<br/>🧠 更智能, 功能丰富"] --> Examples2["典型产品:<br/>Nginx, Envoy, Traefik,<br/>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 策略详解

# 定义 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 终极调优

服务端完整配置

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 调优

// 为 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,     // 顶层总超时
}
// 🐛 经典 Bug: MaxIdleConnsPerHost 默认只有 2!
// 这意味着你访问同一个后端时,最多只有 2 个连接可以复用
// 高并发场景下会频繁创建新连接,浪费 TIME_WAIT
//
// 修复: 显式设置为合理的值(根据 QPS 和连接池大小反推)
transport := &http.Transport{
    MaxIdleConnsPerHost: 50,  // ← 这行不能省!
}

优雅停机模式

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 确保正在处理的请求完成后再退出

关联笔记