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

262 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 负载均衡<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 策略详解
```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 问题