反向代理是服务端架构的咽喉要道——所有流量都从这里经过。代理层的性能天花板就是整个系统的性能天花板。
但代理层的性能调优和应用层不同。应用层的瓶颈通常在业务逻辑和数据库查询;代理层的瓶颈在连接管理、内存分配和 I/O 调度。你面对的不是”慢查询”,而是这些问题:
- 响应体很大时,代理层内存暴涨甚至 OOM
- 后端明明很快,但经过代理后延迟增加了 50ms
- 高并发下代理层 CPU 打满,但后端负载很低
- HTTP/2 客户端通过代理访问 HTTP/1.1 后端,性能反而下降
这些问题的根源都在代理层的缓冲、连接管理和协议转换机制中。本文从 Proxy Buffer、Keepalive、连接复用三个核心维度,系统讲解代理层性能调优的方法论。
一、Proxy Buffer:代理层的内存管理
代理层在客户端和后端之间转发数据时,需要决定:后端的响应数据是缓冲到内存/磁盘后再发给客户端,还是直接透传(流式传输)?
1.1 缓冲 vs 流式传输
缓冲模式(Buffered):代理层接收完后端的完整响应后,再发送给客户端。
客户端 ←── 等待 ──→ [代理层] ←── 快速接收 ──→ 后端
↓ 缓冲区
内存/磁盘
优点: - 后端连接快速释放,不被慢客户端阻塞 - 后端连接利用率高
缺点: - 代理层内存/磁盘消耗大 - 首字节延迟(TTFB)增加
流式传输(Unbuffered / Streaming):代理层收到后端数据后立即转发给客户端。
客户端 ←── 实时转发 ──→ [代理层] ←── 实时接收 ──→ 后端
优点: - TTFB 低,适合大文件和流式响应 - 代理层内存消耗小
缺点: - 慢客户端会阻塞后端连接 - 后端连接占用时间长
1.2 Nginx Proxy Buffer 配置
Nginx 默认启用缓冲模式。核心参数:
location / {
proxy_pass http://backend;
# 是否启用缓冲(默认 on)
proxy_buffering on;
# 响应头缓冲区(第一个 buffer,接收 HTTP 头)
proxy_buffer_size 4k; # 默认 4k 或 8k(取决于平台)
# 响应体缓冲区
proxy_buffers 8 4k; # 8 个 4k 缓冲区 = 32k 内存
proxy_busy_buffers_size 8k; # 可以同时发送给客户端的缓冲区大小
# 当缓冲区满时,溢出到磁盘
proxy_max_temp_file_size 1024m; # 临时文件最大大小
proxy_temp_file_write_size 8k; # 每次写磁盘的大小
proxy_temp_path /var/cache/nginx/proxy_temp;
}
数据流分析:
后端响应到达 Nginx:
1. 响应头 → proxy_buffer_size(4k)
如果头超过 4k → 报错 upstream sent too big header
2. 响应体 → proxy_buffers(8 × 4k = 32k)
├── 填满一个 buffer → 标记为 busy,开始发送给客户端
├── busy_buffers_size 控制同时发送的数据量
└── 所有 buffer 都满了 → 写临时文件
3. 临时文件 → proxy_max_temp_file_size(1024m)
如果超过限制 → 同步转发(变成流式)
1.3 按场景调优 Buffer
场景一:API 网关(小响应体,低延迟)
location /api/ {
proxy_pass http://api_backend;
proxy_buffering on;
# API 响应通常 < 64k
proxy_buffer_size 8k; # 响应头可能包含大 Cookie
proxy_buffers 4 16k; # 4 × 16k = 64k
proxy_busy_buffers_size 32k;
# 不用磁盘缓冲
proxy_max_temp_file_size 0;
}
场景二:文件下载服务(大响应体)
location /download/ {
proxy_pass http://file_backend;
# 大文件用流式传输,避免内存爆炸
proxy_buffering off;
# 或者用较大缓冲区 + 磁盘
# proxy_buffering on;
# proxy_buffers 16 64k;
# proxy_max_temp_file_size 2048m;
}
场景三:SSE / WebSocket(长连接流式)
location /events {
proxy_pass http://sse_backend;
# 必须关闭缓冲,否则事件会被延迟
proxy_buffering off;
proxy_cache off;
# 禁用响应缓冲
proxy_http_version 1.1;
proxy_set_header Connection "";
# 长超时
proxy_read_timeout 3600s;
}
1.4 Nginx 请求体缓冲
代理层也需要缓冲客户端发送的请求体(POST / PUT 数据):
# 客户端请求体缓冲
client_body_buffer_size 16k; # 请求体内存缓冲区
client_max_body_size 100m; # 最大请求体大小
client_body_temp_path /var/cache/nginx/client_temp;
# 当请求体 > client_body_buffer_size 时写磁盘
# 设为 0 则始终写磁盘(节省内存但增加 I/O)
大文件上传场景的优化:
location /upload {
proxy_pass http://upload_backend;
# 允许大文件
client_max_body_size 2g;
# 不在 Nginx 层缓冲请求体,直接流式传给后端
proxy_request_buffering off;
# 增加超时
proxy_read_timeout 600s;
client_body_timeout 600s;
}
1.5 HAProxy 缓冲行为
HAProxy 的缓冲模型与 Nginx 不同。HAProxy 默认不做完整响应缓冲,而是使用固定大小的 Channel Buffer:
global
# 每个连接的缓冲区大小(请求 + 响应各一个)
tune.bufsize 16384 # 默认 16k
# 最大请求/响应头大小(必须 <= bufsize)
tune.maxrewrite 1024 # 头部改写预留空间
# HTTP/2 相关
tune.h2.header-table-size 4096
tune.h2.max-concurrent-streams 100
defaults
# 超时控制
timeout client 30s # 客户端不活动超时
timeout server 30s # 后端不活动超时
timeout tunnel 3600s # WebSocket/隧道超时
# 请求体大小限制
# 注意:HAProxy 对请求体无全局限制,需在 frontend 中配置
HAProxy 的缓冲是 per-connection 的固定大小,不会动态增长也不会写磁盘。这意味着:
- 大响应体自动流式传输
- 内存使用可预测:
连接数 × bufsize × 2 - 不存在 OOM 风险(但可能存在性能问题)
# 计算 HAProxy 内存使用
# 假设 10,000 并发连接,bufsize 16k
# 内存 = 10000 × 16k × 2 = 320 MB(仅缓冲区)
# 加上连接控制块等开销,约 400-500 MB1.6 Envoy Buffer 配置
Envoy 的缓冲通过 HTTP Connection Manager 和 Buffer Filter 控制:
# Envoy buffer 配置
http_filters:
- name: envoy.filters.http.buffer
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer
max_request_bytes: 1048576 # 最大请求体缓冲 1MB
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# HTTP Connection Manager 级别
http_connection_manager:
stream_idle_timeout: 300s
request_timeout: 60s
# 每个流的缓冲区限制
per_stream_buffer_limit_bytes: 32768 # 32kEnvoy 默认是流式处理的——数据到达后立即转发。Buffer Filter 可以改变这个行为,将请求体完整缓冲后再转发。
二、Keepalive:连接复用的核心
HTTP Keepalive 让多个请求复用同一个 TCP 连接,避免了重复的 TCP 握手和 TLS 协商。在代理层,Keepalive 涉及两个方向:
- 下游 Keepalive:客户端到代理层的连接复用
- 上游 Keepalive:代理层到后端的连接复用
2.1 下游 Keepalive(客户端 → 代理)
# Nginx 下游 Keepalive
http {
# 一个 keepalive 连接上最多处理的请求数
keepalive_requests 1000; # 默认 1000
# keepalive 连接的空闲超时
keepalive_timeout 75s; # 默认 75s
# keepalive_timeout 的第二个参数:发送给客户端的 Keep-Alive 头
# keepalive_timeout 75s 60s;
# 75s = 服务端实际超时
# 60s = 告诉客户端的超时(Keep-Alive: timeout=60)
}
关键问题:keepalive_timeout 应该比下游负载均衡器的 idle timeout 短。
常见坑:
AWS ALB idle timeout: 60s(默认)
Nginx keepalive_timeout: 75s(默认)
问题:ALB 在 60s 时关闭连接,但 Nginx 认为连接还活着
→ Nginx 在已关闭的连接上发送响应 → 502 Bad Gateway
解决:
keepalive_timeout 55s; # 比 ALB 的 60s 短
2.2 上游 Keepalive(代理 → 后端)
上游 Keepalive 是代理性能的关键——每次新建 TCP 连接到后端都需要 1 RTT(TCP 握手),如果后端是 HTTPS 还需要额外 1-2 RTT(TLS 握手)。
Nginx 上游 Keepalive:
upstream backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
# 每个 worker 与每个 upstream 保持的空闲连接数
keepalive 64;
# keepalive 连接上最多处理的请求数(Nginx 1.15.3+)
keepalive_requests 1000;
# keepalive 连接的空闲超时(Nginx 1.15.3+)
keepalive_timeout 60s;
# 注意:keepalive 64 是 per-worker 的
# 4 个 worker × 64 = 最多 256 个空闲连接到这个 upstream
}
server {
location / {
proxy_pass http://backend;
# 必须设置 HTTP/1.1 才能使用上游 keepalive
proxy_http_version 1.1;
proxy_set_header Connection "";
# 不要设 proxy_set_header Connection "keep-alive"
# 因为 HTTP/1.1 默认就是 keep-alive
}
}
常见错误:忘记设置
proxy_http_version 1.1 和清除 Connection
头:
# 错误配置(默认 HTTP/1.0,每个请求新建连接)
location / {
proxy_pass http://backend;
# 没有设置 proxy_http_version 和 Connection
# → Nginx 用 HTTP/1.0 连后端
# → HTTP/1.0 默认 Connection: close
# → keepalive 完全无效
}
# 另一个错误(转发了客户端的 Connection 头)
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
# 没有清除 Connection 头
# → 客户端发 Connection: keep-alive
# → 被原样转发给后端
# → 某些后端会困惑
}
2.3 HAProxy Keepalive 配置
defaults
# 下游连接管理
option http-keep-alive # 启用下游 keepalive(默认)
# option httpclose # 强制关闭(调试用)
# option http-server-close # 仅保持下游 keepalive,上游每次关闭
timeout http-keep-alive 10s # keepalive 空闲超时
backend app_servers
balance roundrobin
# 上游 keepalive(HAProxy 2.4+)
# default-server 级别设置
default-server inter 3s fall 3 rise 2
server app1 10.0.1.10:8080 check
server app2 10.0.1.11:8080 check
HAProxy 的 HTTP 连接模式需要理解三种模式的区别:
| 模式 | 下游 Keepalive | 上游 Keepalive | 说明 |
|---|---|---|---|
http-keep-alive |
是 | 是 | 默认模式,最佳性能 |
http-server-close |
是 | 否 | 每次请求关闭上游连接 |
httpclose |
否 | 否 | 每次请求关闭所有连接 |
2.4 Keepalive 参数调优方法论
Keepalive 参数不是越大越好。参数设置需要基于实际流量特征:
# 1. 统计当前连接复用率
# Nginx 上游连接状态
curl -s http://localhost:8080/nginx_status
# Active connections: 1024
# server accepts handled requests
# 15000 15000 850000
# Reading: 10 Writing: 50 Waiting: 964
# Waiting = 空闲 keepalive 连接数
# 如果 Waiting 接近 0 → keepalive 池太小
# 如果 Waiting 很大 → keepalive 池太大,浪费内存
# 2. 观察上游连接建立率
ss -s
# TCP: 2048 (estab 1024, closed 128, orphaned 0, timewait 512)
# timewait 高 → 连接频繁关闭重建 → keepalive 参数需要调大
# 3. 观察 TIME_WAIT 连接数
ss -tan state time-wait | wc -l
# 如果 > 1000 → 说明连接复用不足Keepalive 参数公式(经验值):
上游 keepalive 连接数 ≈
(峰值 QPS / Worker 数) × 平均响应时间(秒) × 1.5
示例:
- 峰值 QPS: 10,000
- Worker 数: 4
- 平均响应时间: 50ms = 0.05s
- keepalive = (10000 / 4) × 0.05 × 1.5 = 187.5 → 设置 keepalive 200;
验证:
- 如果 TIME_WAIT 仍然很多 → 增大
- 如果空闲连接太多 → 减小
- 通过 upstream status 监控调整
2.5 Keepalive 与负载均衡的冲突
Keepalive 连接会与某些负载均衡算法产生冲突:
场景:Round-Robin + Keepalive
预期行为:请求均匀分到 Server A 和 Server B
实际行为:大部分请求都发给了 Server A
原因:
1. Worker 1 建立到 Server A 的 keepalive 连接
2. 下一个请求来了 → 复用已有的 keepalive 连接到 Server A
3. 只有 keepalive 连接都在忙时,才会新建到 Server B 的连接
4. 结果:Server A 收到的请求远多于 Server B
解决方案:
upstream backend {
# 方案一:减小 keepalive 数量,增加新建连接的机会
keepalive 8; # 而不是 64
# 方案二:使用 least_conn 替代 round_robin
least_conn;
keepalive 64;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
三、HTTP/2 代理行为
HTTP/2 引入了多路复用(Multiplexing)——单个 TCP 连接上可以并行传输多个请求/响应。这对代理层的行为有根本性影响。
3.1 HTTP/2 下游 + HTTP/1.1 上游
最常见的部署模式:客户端用 HTTP/2 连接代理层,代理层用 HTTP/1.1 连接后端。
客户端 ──HTTP/2──→ [代理层] ──HTTP/1.1──→ 后端
1 条连接 协议转换 N 条连接
多路复用 每个请求一条连接
这个模式下的行为:
server {
listen 443 ssl http2; # 下游 HTTP/2
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 上游 HTTP/1.1
proxy_set_header Connection "";
}
}
客户端在一条 HTTP/2 连接上发送 100 个并发请求,代理层需要同时建立/复用最多 100 条 HTTP/1.1 连接到后端。
性能影响:
客户端侧:1 条 TCP 连接 + 1 次 TLS 握手
代理层侧:最多 100 条 TCP 连接到后端
如果后端处理慢(100ms/请求),100 个并发请求需要:
- 100 条上游连接
- 需要 keepalive 池足够大
- 否则频繁建立/关闭连接 → 性能退化
限制并发流数量:
http {
# 限制单个 HTTP/2 连接上的并发流数量
http2_max_concurrent_streams 128; # 默认 128
# 如果后端扛不住太多并发,可以减小这个值
# http2_max_concurrent_streams 32;
}
3.2 HTTP/2 下游 + HTTP/2 上游
部分代理支持与后端也使用 HTTP/2,保持端到端多路复用:
客户端 ──HTTP/2──→ [代理层] ──HTTP/2──→ 后端
1 条连接 1 条连接 1 条连接
多路复用 多路复用 多路复用
Envoy 原生支持 HTTP/2 上游:
clusters:
- name: backend_h2
connect_timeout: 5s
type: STRICT_DNS
http2_protocol_options:
max_concurrent_streams: 100
initial_stream_window_size: 1048576 # 1MB
initial_connection_window_size: 1048576
load_assignment:
cluster_name: backend_h2
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend.internal
port_value: 8080Nginx gRPC 代理(HTTP/2 上游):
upstream grpc_backend {
server 10.0.1.10:50051;
keepalive 64;
}
server {
listen 443 ssl http2;
location /grpc.service/ {
grpc_pass grpc://grpc_backend;
# gRPC 超时
grpc_read_timeout 300s;
grpc_send_timeout 300s;
}
}
3.3 HTTP/2 多路复用对负载均衡的影响
HTTP/2 多路复用在代理层会产生负载不均的问题:
问题:HTTP/2 + Keepalive = 流量集中到少数连接
客户端 1 ──HTTP/2──→ [代理层] ──保持连接──→ Server A
客户端 2 ──HTTP/2──→ [代理层] ──保持连接──→ Server A
客户端 3 ──HTTP/2──→ [代理层] ──保持连接──→ Server B
结果:Server A 收到 2/3 的流量,Server B 只有 1/3
Envoy 的解决方案:
clusters:
- name: backend
lb_policy: LEAST_REQUEST # 基于请求数而非连接数
http2_protocol_options:
max_concurrent_streams: 100
# 连接池设置
circuit_breakers:
thresholds:
- max_connections: 1024
max_pending_requests: 1024
max_requests: 1024gRPC 客户端负载均衡(非代理方案):
// Go gRPC 客户端侧负载均衡
import (
"google.golang.org/grpc"
"google.golang.org/grpc/balancer/roundrobin"
)
conn, err := grpc.Dial(
"dns:///backend.internal:50051",
grpc.WithDefaultServiceConfig(
`{"loadBalancingPolicy":"round_robin"}`,
),
)3.4 HTTP/2 流控的代理影响
HTTP/2 有流级别和连接级别的流量控制窗口。代理层需要正确管理两端的窗口:
客户端 ──窗口 A──→ [代理层] ──窗口 B──→ 后端
如果 窗口 A < 窗口 B:
客户端接收慢 → 代理层缓冲后端数据 → 内存增长
如果 窗口 A > 窗口 B:
后端发送慢 → 代理层需要等待后端 → 延迟增加
Envoy 的流控窗口配置:
# 调整 HTTP/2 流控窗口
http_connection_manager:
http2_protocol_options:
initial_stream_window_size: 1048576 # 每个流的窗口 1MB
initial_connection_window_size: 1048576 # 连接级窗口 1MB四、连接管理与超时
代理层的超时配置是性能和可靠性的关键。超时太短会导致合法请求被截断,太长会导致资源泄漏。
4.1 Nginx 超时全景
http {
# === 下游超时(客户端 → Nginx)===
# 客户端发送请求头的超时
client_header_timeout 60s; # 默认 60s
# 客户端发送请求体的超时(两次 read 操作之间)
client_body_timeout 60s; # 默认 60s
# 发送响应给客户端的超时(两次 write 操作之间)
send_timeout 60s; # 默认 60s
# keepalive 空闲超时
keepalive_timeout 75s; # 默认 75s
# === 上游超时(Nginx → 后端)===
# 与后端建立连接的超时
proxy_connect_timeout 60s; # 默认 60s
# 从后端读取响应的超时(两次 read 操作之间)
proxy_read_timeout 60s; # 默认 60s
# 向后端发送请求的超时(两次 write 操作之间)
proxy_send_timeout 60s; # 默认 60s
# === 关键注意 ===
# 这些超时都是"两次操作之间"的间隔,不是"总时间"
# 一个持续发送数据的慢请求不会触发超时
}
请求生命周期中的超时分布:
客户端连接 ─────────────────────────────────────────→ 断开
│ │
├─ client_header_timeout ─┤ │
│ 接收请求头 │ │
│ ├─ client_body_timeout ─┤ │
│ │ 接收请求体 │ │
│ │ ├─ proxy_connect_timeout ─┤
│ │ │ 连接后端 │
│ │ │ ├─ proxy_send_timeout ──┤
│ │ │ │ 发送请求到后端 │
│ │ │ │ ├─ proxy_read_timeout ─┤
│ │ │ │ │ 接收后端响应 │
│ │ │ │ │ ├─ send_timeout ─┤
│ │ │ │ │ │ 发送响应到客户端│
4.2 超时调优指南
# 场景一:API 网关(低延迟、快速失败)
location /api/ {
proxy_connect_timeout 3s; # 后端连不上就快速失败
proxy_read_timeout 10s; # API 应该 10s 内返回
proxy_send_timeout 10s;
client_body_timeout 10s;
send_timeout 10s;
}
# 场景二:文件上传
location /upload {
proxy_connect_timeout 5s;
proxy_read_timeout 600s; # 大文件处理耗时
proxy_send_timeout 600s;
client_body_timeout 600s; # 慢上传
client_max_body_size 2g;
}
# 场景三:WebSocket
location /ws {
proxy_read_timeout 3600s; # 长连接
proxy_send_timeout 3600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 场景四:gRPC 流式调用
location /grpc {
grpc_read_timeout 3600s; # 流式 RPC 可能持续很长时间
grpc_send_timeout 3600s;
client_body_timeout 3600s;
}
4.3 HAProxy 超时体系
defaults
# 客户端连接超时(TCP 层)
timeout client 30s
# 等待客户端发送完整请求的超时
timeout http-request 10s
# keepalive 空闲超时
timeout http-keep-alive 10s
# 后端连接超时
timeout connect 5s
# 后端响应超时
timeout server 30s
# 隧道/WebSocket 超时
timeout tunnel 3600s
# 队列等待超时(当所有后端都忙时)
timeout queue 30s
# 重试间隔
timeout check 5s
4.4 连接池管理
upstream backend {
server 10.0.1.10:8080 max_conns=200; # 单个后端最大连接数
server 10.0.1.11:8080 max_conns=200;
keepalive 64; # 空闲连接数
keepalive_requests 1000; # 每个连接最多请求数
keepalive_timeout 60s; # 空闲超时
# 排队配置(Nginx Plus / 开源不支持)
# queue 100 timeout=30s;
}
连接数限制的计算:
后端单机最大连接数 = worker_connections × worker_processes
示例:
- Nginx: 4 workers × 1024 connections = 4096 最大并发
- 后端单机承载: 200 连接
- 后端数量: 4 台
- max_conns = 200 per backend server
- 总上游连接 = 200 × 4 = 800
- 需要确保 800 < 4096(Nginx 侧有余量)
4.5 连接泄漏检测
连接泄漏是代理层的常见问题——连接建立后没有正确关闭,最终耗尽连接池:
# 1. 检查当前连接状态分布
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 1024 ESTAB
# 512 TIME-WAIT
# 64 CLOSE-WAIT ← 关注这个
# 8 FIN-WAIT-2
# CLOSE-WAIT 多 → 对端已关闭但本端没有 close()
# TIME-WAIT 多 → 正常,但过多可能需要调优
# FIN-WAIT-2 多 → 对端没有完成关闭握手
# 2. 按目标地址统计连接
ss -tan state established dst 10.0.1.10 | wc -l
ss -tan state established dst 10.0.1.11 | wc -l
# 3. 监控 Nginx 活动连接数趋势
while true; do
active=$(curl -s http://localhost:8080/nginx_status | awk '/Active/{print $3}')
echo "$(date '+%H:%M:%S') Active: $active"
sleep 5
done
# 4. 如果使用 Nginx Plus 或 OpenResty
# 可以查看 upstream 的连接池状态
curl -s http://localhost:8080/api/status/upstreams | python3 -m json.tool五、CPU 与内核调优
代理层的 CPU 使用主要来自三个方面:TLS 加解密、数据拷贝和系统调用。
5.1 Worker 进程绑定 CPU
# Nginx worker 绑定 CPU
worker_processes auto; # 自动设为 CPU 核数
worker_cpu_affinity auto; # 自动绑定 CPU
# 或手动指定(4 核场景)
# worker_processes 4;
# worker_cpu_affinity 0001 0010 0100 1000;
5.2 事件模型调优
events {
worker_connections 4096; # 每个 worker 的最大连接数
use epoll; # Linux 使用 epoll
multi_accept on; # 一次性接受所有新连接
# accept_mutex(Nginx 1.11.3+ 默认 off)
# off = 所有 worker 都监听,使用 SO_REUSEPORT
# on = 只有一个 worker 获取锁来接受连接
accept_mutex off;
}
SO_REUSEPORT:
server {
# reuseport 让每个 worker 有独立的 listen socket
# 内核负责均匀分发连接到各 worker
listen 443 ssl http2 reuseport;
}
SO_REUSEPORT 的效果:
# 无 reuseport:所有 worker 竞争同一个 socket
# 有 reuseport:每个 worker 独立 socket,内核调度
# 高并发场景下 reuseport 的性能提升
# 测试:4 核,1000 并发短连接
# 无 reuseport: 45,000 req/s
# 有 reuseport: 58,000 req/s (+29%)5.3 TLS 性能优化
TLS 是代理层最大的 CPU 消耗者。优化方向:
# 1. 使用 ECDSA 证书(比 RSA 快 3-10 倍)
ssl_certificate /etc/nginx/certs/ecdsa.crt;
ssl_certificate_key /etc/nginx/certs/ecdsa.key;
# 2. 使用 TLS 1.3(握手更快)
ssl_protocols TLSv1.2 TLSv1.3;
# 3. Session 复用减少完整握手
ssl_session_cache shared:TLS:10m;
ssl_session_timeout 1d;
# 4. OCSP Stapling(减少客户端的 OCSP 查询)
ssl_stapling on;
ssl_stapling_verify on;
验证 AES-NI 硬件加速:
# 检查 CPU 是否支持 AES-NI
grep -o aes /proc/cpuinfo | head -1
# 检查 OpenSSL 是否使用 AES-NI
openssl speed -evp aes-128-gcm
# 有 AES-NI: ~5 GB/s
# 无 AES-NI: ~0.5 GB/s(差 10 倍)
# 检查 OpenSSL 版本和编译选项
openssl version -a
nginx -V 2>&1 | grep -o "built with OpenSSL[^\"]*"5.4 内核网络参数
# /etc/sysctl.d/99-proxy-tuning.conf
# 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 文件描述符
fs.file-max = 1000000
# TIME_WAIT 优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# 端口范围(代理层作为客户端连后端需要大量端口)
net.ipv4.ip_local_port_range = 1024 65535
# TCP 缓冲区自动调优
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# TCP Keepalive(代理层到后端的连接保活)
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3# 应用参数
sysctl -p /etc/sysctl.d/99-proxy-tuning.conf
# Nginx worker 文件描述符限制
# /etc/nginx/nginx.conf
worker_rlimit_nofile 65535;
# 或在 systemd unit 中设置
# [Service]
# LimitNOFILE=65535六、监控与容量规划
6.1 核心监控指标
代理层需要监控的关键指标:
| 指标 | 来源 | 告警阈值(参考) |
|---|---|---|
| 活跃连接数 | stub_status | > 80% worker_connections |
| 请求延迟 P99 | access_log | > 基线 3 倍 |
| 上游响应时间 | upstream_response_time | > SLA 阈值 |
| 5xx 错误率 | status code | > 1% |
| 上游连接失败 | error_log | > 0 |
| CPU 使用率 | 系统指标 | > 80% |
| 内存使用率 | 系统指标 | > 85% |
| 打开文件描述符数 | /proc/pid/fd | > 80% limit |
6.2 Nginx 监控配置
# stub_status 模块(基础指标)
server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
使用 nginx-prometheus-exporter 采集指标:
# 安装 nginx-prometheus-exporter
# 从 GitHub releases 下载对应版本
# 启动 exporter
nginx-prometheus-exporter \
-nginx.scrape-uri=http://127.0.0.1:8080/nginx_status \
-web.listen-address=:9113Nginx 访问日志中记录性能指标:
log_format performance '$remote_addr - $request_time '
'$upstream_response_time $upstream_connect_time '
'$upstream_header_time $status $body_bytes_sent '
'"$request" "$upstream_addr"';
access_log /var/log/nginx/perf.log performance;
# 各时间字段含义:
# $request_time — 从接收第一个字节到发送最后一个字节的总时间
# $upstream_response_time — 从连接后端到接收完响应的时间
# $upstream_connect_time — 与后端建立连接的时间
# $upstream_header_time — 从连接后端到接收响应头的时间
分析延迟分布:
# 统计 P50 / P95 / P99 延迟
awk '{print $3}' /var/log/nginx/perf.log | sort -n | \
awk '{
a[NR] = $1
} END {
p50 = a[int(NR*0.5)]
p95 = a[int(NR*0.95)]
p99 = a[int(NR*0.99)]
printf "P50: %ss P95: %ss P99: %ss Max: %ss\n", p50, p95, p99, a[NR]
}'
# 按后端服务器统计平均响应时间
awk '{print $NF, $3}' /var/log/nginx/perf.log | \
awk -F'[" ]' '{
sum[$1] += $2; count[$1]++
} END {
for (s in sum) printf "%s avg: %.3fs (%d requests)\n", s, sum[s]/count[s], count[s]
}'6.3 容量规划
代理层容量规划的核心公式:
最大并发连接数 = worker_processes × worker_connections
每连接内存 ≈
proxy_buffer_size
+ proxy_buffers × buffer_size
+ 连接控制块(~5-10 KB)
+ TLS 会话内存(~50 KB,若启用 TLS)
总内存需求 = 最大并发连接数 × 每连接内存
示例:
- 4 workers × 4096 connections = 16,384 最大并发
- 每连接:4k + 8×4k + 10k + 50k = 96 KB(TLS 场景)
- 总内存:16,384 × 96 KB ≈ 1.5 GB
CPU 需求:
- 纯 HTTP 代理:每核约 50,000-80,000 req/s
- TLS 终止(RSA 2048):每核约 5,000-8,000 新连接/s
- TLS 终止(ECDSA P-256):每核约 15,000-25,000 新连接/s
- TLS 复用连接上的请求:每核约 30,000-50,000 req/s
6.4 性能基准测试方法
# 1. 使用 wrk 测试吞吐量
wrk -t4 -c400 -d30s https://proxy.example.com/api/health
# Requests/sec: 45,230
# Transfer/sec: 12.5 MB
# Latency (avg/max): 8.84ms / 245ms
# 2. 使用 wrk2 测试延迟分布(固定 QPS)
wrk2 -t4 -c100 -d60s -R10000 https://proxy.example.com/api/health
# 固定 10,000 QPS 下的延迟分布
# 3. 使用 h2load 测试 HTTP/2 性能
h2load -n100000 -c100 -m10 https://proxy.example.com/api/health
# -m10: 每个连接 10 个并发流
# 4. 使用 vegeta 进行持续负载测试
echo "GET https://proxy.example.com/api/health" | \
vegeta attack -rate=5000 -duration=60s | \
vegeta report
# 输出成功率、延迟分布、状态码分布
# 5. TLS 握手性能测试
openssl s_time -connect proxy.example.com:443 -new -time 30
# 30 秒内完成的新 TLS 握手数七、总结
代理层性能调优不是”改几个参数”就完事的。它需要理解数据在代理层的完整流转路径,找到真正的瓶颈,然后对症下药。
我的工程建议:
Buffer 按场景区分。API 网关用小 buffer + 不写磁盘;文件服务用流式传输或大 buffer + 磁盘缓冲;SSE/WebSocket 必须关闭缓冲。不要用一套配置应对所有场景。
上游 Keepalive 必须正确配置。
proxy_http_version 1.1+proxy_set_header Connection ""是必须的。keepalive 池大小根据公式计算,通过监控 TIME_WAIT 数量验证。HTTP/2 代理需要注意连接亲和性。HTTP/2 多路复用 + Keepalive 会导致流量集中到少数后端。使用
least_conn而不是round_robin,或者控制 keepalive 池大小。超时链路要一致。代理层的
proxy_read_timeout应该大于后端的最大处理时间,但小于客户端的超时。不同的 location(API / 上传 / WebSocket)应该配置不同的超时。监控优先于调优。没有监控数据的调优是盲调。先部署 stub_status + access_log 性能字段 + Prometheus exporter,然后基于数据调优。
容量规划要考虑 TLS 开销。TLS 的内存开销(每连接 50KB)和 CPU 开销(新建连接的握手)是代理层最大的资源消耗。ECDSA 证书和 TLS 会话复用是性价比最高的优化。
下一篇:网关选型对比:Nginx vs HAProxy vs Envoy vs Traefik
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】网关选型对比:Nginx vs HAProxy vs Envoy vs Traefik
从架构模型、配置方式、协议支持、可观测性、扩展机制和性能特征六个维度,系统对比 Nginx、HAProxy、Envoy 和 Traefik 四大代理/网关,给出基于场景的选型依据。
【网络工程】反向代理模式:TLS 终止、透传与重加密
系统解剖反向代理的三种 TLS 处理模式——终止、透传与重加密。从架构对比到 SNI 路由、证书管理、性能影响与安全权衡,给出生产环境的工程选型依据。
【网络工程】Envoy 架构剖析:xDS、Filter Chain 与热重启
系统剖析 Envoy 代理的架构设计:per-Worker 线程模型与事件循环、Filter Chain 的分层扩展机制、xDS 协议族的动态配置发现、热重启的实现原理与零停机更新、Envoy 在 Service Mesh 中的数据面角色,建立 Envoy 从架构到运维的完整理解。
【网络工程】零拷贝网络:sendfile、splice 与 MSG_ZEROCOPY
数据从磁盘到网卡的传统路径涉及 4 次拷贝和多次上下文切换。本文系统剖析 sendfile、splice、vmsplice、MSG_ZEROCOPY 四种零拷贝技术的内核实现、适用场景与性能差异,并以 Kafka 和 Nginx 为案例分析零拷贝在生产系统中的工程实践。