土法炼钢兴趣小组的算法知识备份

【网络工程】代理性能调优:缓冲、Keepalive 与连接复用

文章导航

分类入口
network
标签入口
#proxy#performance#nginx#haproxy#envoy#keepalive

目录

反向代理是服务端架构的咽喉要道——所有流量都从这里经过。代理层的性能天花板就是整个系统的性能天花板。

但代理层的性能调优和应用层不同。应用层的瓶颈通常在业务逻辑和数据库查询;代理层的瓶颈在连接管理、内存分配和 I/O 调度。你面对的不是”慢查询”,而是这些问题:

这些问题的根源都在代理层的缓冲、连接管理和协议转换机制中。本文从 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 的固定大小,不会动态增长也不会写磁盘。这意味着:

# 计算 HAProxy 内存使用
# 假设 10,000 并发连接,bufsize 16k
# 内存 = 10000 × 16k × 2 = 320 MB(仅缓冲区)
# 加上连接控制块等开销,约 400-500 MB

1.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  # 32k

Envoy 默认是流式处理的——数据到达后立即转发。Buffer Filter 可以改变这个行为,将请求体完整缓冲后再转发。

二、Keepalive:连接复用的核心

HTTP Keepalive 让多个请求复用同一个 TCP 连接,避免了重复的 TCP 握手和 TLS 协商。在代理层,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: 8080

Nginx 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: 1024

gRPC 客户端负载均衡(非代理方案):

// 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=:9113

Nginx 访问日志中记录性能指标:

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 握手数

七、总结

代理层性能调优不是”改几个参数”就完事的。它需要理解数据在代理层的完整流转路径,找到真正的瓶颈,然后对症下药。

我的工程建议

  1. Buffer 按场景区分。API 网关用小 buffer + 不写磁盘;文件服务用流式传输或大 buffer + 磁盘缓冲;SSE/WebSocket 必须关闭缓冲。不要用一套配置应对所有场景。

  2. 上游 Keepalive 必须正确配置proxy_http_version 1.1 + proxy_set_header Connection "" 是必须的。keepalive 池大小根据公式计算,通过监控 TIME_WAIT 数量验证。

  3. HTTP/2 代理需要注意连接亲和性。HTTP/2 多路复用 + Keepalive 会导致流量集中到少数后端。使用 least_conn 而不是 round_robin,或者控制 keepalive 池大小。

  4. 超时链路要一致。代理层的 proxy_read_timeout 应该大于后端的最大处理时间,但小于客户端的超时。不同的 location(API / 上传 / WebSocket)应该配置不同的超时。

  5. 监控优先于调优。没有监控数据的调优是盲调。先部署 stub_status + access_log 性能字段 + Prometheus exporter,然后基于数据调优。

  6. 容量规划要考虑 TLS 开销。TLS 的内存开销(每连接 50KB)和 CPU 开销(新建连接的握手)是代理层最大的资源消耗。ECDSA 证书和 TLS 会话复用是性价比最高的优化。


上一篇:反向代理模式:TLS 终止、透传与重加密

下一篇:网关选型对比:Nginx vs HAProxy vs Envoy vs Traefik

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-09-03 · network

【网络工程】Envoy 架构剖析:xDS、Filter Chain 与热重启

系统剖析 Envoy 代理的架构设计:per-Worker 线程模型与事件循环、Filter Chain 的分层扩展机制、xDS 协议族的动态配置发现、热重启的实现原理与零停机更新、Envoy 在 Service Mesh 中的数据面角色,建立 Envoy 从架构到运维的完整理解。

2025-07-23 · network

【网络工程】零拷贝网络:sendfile、splice 与 MSG_ZEROCOPY

数据从磁盘到网卡的传统路径涉及 4 次拷贝和多次上下文切换。本文系统剖析 sendfile、splice、vmsplice、MSG_ZEROCOPY 四种零拷贝技术的内核实现、适用场景与性能差异,并以 Kafka 和 Nginx 为案例分析零拷贝在生产系统中的工程实践。


By .