Files
cs-note/hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用.md
T
2026-05-24 11:42:38 +08:00

7.1 KiB
Raw Blame History

tags, create time
tags create time
计算机网络
TCP调优
sysctl
BBR
TIME_WAIT
KeepAlive
连接复用
2026-05-18 05:15

TCP 内核调优与连接复用

概述

网络性能优化的核心公式是:用户体验 = ∑(RTT_i × 系数_i) + 数据量 / 实际吞吐量。本章从操作系统内核参数入手,到传输层之上的 HTTP/gRPC keep-alive,逐层展开优化手段。

TCP 内核参数全面调优

参数速查表(按优先级排序)

# ⭐ 第一优先级 — 这些不改,高并发必瓶颈
$ sysctl net.core.somaxconn              # socket 监听 backlog 上限 (默认 128 → 改为 65535!)
$ sysctl net.ipv4.tcp_max_syn_backlog    # SYN 半连接队列大小 (默认 ~1024 → 改为 8192)
$ sysctl net.ipv4.tcp_congestion_control # 拥塞控制算法 (默认 cubic → bbr)

# ⭐ 第二优先级 — 缓冲区大小决定高带宽延迟积场景的吞吐量
$ sysctl net.core.rmem_max               # max recv buffer (默认 212992 → 改为 16MB)
$ sysctl net.core.wmem_max               # max send buffer (默认 212992 → 改为 16MB)
$ sysctl net.ipv4.tcp_rmem               # auto/min/max recv buffer (默认 4096 87380 6291456)
$ sysctl net.ipv4.tcp_wmem               # auto/min/max send buffer

# ⭐ 第三优先级 — TIME_WAIT 管理
$ sysctl net.ipv4.tcp_tw_reuse           # 复用 TIME_WAIT 套接字做 outbound (默认 0 → 改为 1)
$ ⚠️ tcp_tw_recycle 已从 Linux 4.12 永久移除!不要尝试启用
# 第四优先级 — 高级功能
$ sysctl net.ipv4.tcp_fastopen           # TCP Fast Open (0=off, 3=enable both)
$ sysctl net.ipv4.tcp_timestamps         # 时间戳选项 (SACK/BBR 需要, 默认 1)
$ sysctl net.ipv4.tcp_sack               # Selective ACK (默认 1, 别关)
$ sysctl net.ipv4.tcp_fack               # Forward ACK (配合 SACK)
$ sysctl net.ipv4.tcp_limit_output_bytes # TCP 输出队列限制
# 第五优先级 — Keepalive 调优
$ sysctl net.ipv4.tcp_keepalive_time     # 首次探测前的空闲时间 (默认 7200s → 改为 600s)
$ sysctl net.ipv4.tcp_keepalive_intvl    # 两次探测间隔 (默认 75s → 改为 30s)
$ sysctl net.ipv4.tcp_keepalive_probes   # 最多探测次数 (默认 9 → 改为 3)
# 总断开时间 = tcp_keepalive_time + (tcp_keepalive_intvl × tcp_keepalive_probes)
# 原来: 7200 + (75 × 9) = 7875s ≈ 2.2h
# 新值: 600 + (30 × 3) = 690s ≈ 11.5min

# 端口范围(连接池场景下扩大临时端口)
$ sysctl net.ipv4.ip_local_port_range    # 默认 32768-60999 → 改为 1024-65535

/etc/sysctl.conf 生产级持久化配置

# ==========================================
# Web/API 服务器 TCP 调优配置
# ==========================================

# === 连接队列 ===
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_abort_on_overflow = 0

# === 缓冲区 (BDP 适配: Buffer = Bandwidth × RTT) ===
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 16777216
net.core.wmem_default = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# === 拥塞控制算法 ===
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_mux = 1                    # BBR 推荐开启 multiplex

# === TIME_WAIT ===
net.ipv4.tcp_tw_reuse = 1

# === Keepalive ===
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

# === 端口范围 ===
net.ipv4.ip_local_port_range = 1024 65535

# === 高级特性 ===
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
# 生效配置
$ sudo sysctl -p
$ # 验证拥塞控制算法
$ cat /proc/net/sockstat
$ ss -i  # 查看当前连接的拥塞控制算法

Keep-Alive 与连接复用全栈方案

为什么 connection reuse 这么重要?

一次全新 TCP 连接的开销:
─────────────────────────────
TCP 三次握手     = 1 RTT (TCP Fast Open 可 0-RTT)
TLS 握手 TLS1.3 = 1 RTT (Session Resumption 可 0-RTT)
HTTP Request    = 1 RTT
─────────────────────────────
总计            = 2~3 RTT

如果复用连接:
─────────────────────────────
已有连接上新请求 = 0 RTT (零额外等待)
───────────────────────────────────
每次请求节省 2~3 RTT!
对于跨洋链路 (RTT ≈ 150ms), 每次请求省 450ms!

HTTP Keep-Alive —— Go 客户端侧

// Go http.Transport 的连接池配置
transport := &http.Transport{
    MaxIdleConns:        100,           // 全局最大空闲连接数
    MaxIdleConnsPerHost: 10,            // 每个 host 最大空闲连接数
    IdleConnTimeout:     90 * time.Second, // 空闲超此时间关闭
    
    // TLS 优化: Session Ticket 实现 TLS 1.3 0-RTT resumption
    TLSClientConfig: &tls.Config{
        SessionTicketsDisabled: false,  // 默认开启
    },
    
    // 禁用压缩 header(避免 CRIME/BREACH 攻击)
    DisableCompression: true,
}

client := &http.Client{Transport: transport}
// 后续的 client.Get() 自动复用已建立的 TCP + TLS 连接

HTTP Keep-Alive —— Nginx 反向代理侧

upstream backend {
    server app1:8080;
    server app2:8080;
    keepalive 32;       # 保持 32 个空闲连接到后端
    keepalive_requests 10000;  # 单个连接最多服务 10000 个请求
    keepalive_timeout 60s;      # 空闲超时
}

server {
    location /api {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # 清空 Connection: close
        
        # 关键: Nginx 默认使用 HTTP/1.0 向后端转发,不带 keepalive
        # 必须显式设置 proxy_http_version 1.1 + 清空 Connection header
    }
}

gRPC KeepAlive —— 双向心跳

import "google.golang.org/grpc/keepalive"

grpc.NewServer(
    // 服务端参数
    grpc.KeepaliveParams(keepalive.ServerParameters{
        Time:    10 * time.Second,   // 每 10s 发 ping
        Timeout: 5 * time.Second,    // 等 5s 回复 pong
        MaximumConnectionIdle: 5 * time.Minute, // 最大空闲连接存活时间
    }),
    // 客户端策略强制执行
    grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
        MinTime:             5 * time.Second, // 客户端至少每 5s 发一次 ping
        PermitWithoutStream: true,            // 即使没有活跃流也允许 ping
    }),
)

[!question] gRPC 为什么需要独立于 HTTP/2 的 KeepAlive? HTTP/2 本身有 PING frame,但 gRPC 在应用层额外加了一层——这是因为中间可能有 L7 load balancer(如 Envoy),它会主动关闭长连接。gRPC 的 KeepAlive 可以让负载均衡器认为连接仍然活跃。

关联笔记