262 lines
8.9 KiB
Markdown
262 lines
8.9 KiB
Markdown
|
|
---
|
|||
|
|
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 问题
|