前面几篇文章分别讲了 TCP
的连接管理、可靠传输、流量控制和拥塞控制。每一篇都提到了一些内核参数。但这些参数之间有复杂的依赖关系——改一个可能影响另一个。盲目调参就像在迷宫里随机转弯,改了
tcp_rmem 却忘了 rmem_max
的约束,调了 somaxconn 却不知道应用的
listen() backlog 也得配合。
这篇文章把 TCP 调优作为一个整体来讲。不是罗列参数,而是按工程场景组织——你遇到什么问题,应该调什么参数,调之前怎么看基线,调之后怎么验证效果。
一、调优方法论
先测量,再调优
# 调优的第一条铁律:不要猜,要测
# TCP 调优的标准流程:
# 1. 建立基线(当前性能指标)
# 2. 定位瓶颈(是 CPU、内存、网络、还是应用?)
# 3. 假设根因(如"接收缓冲区太小限制了吞吐量")
# 4. 修改参数
# 5. 测量效果
# 6. 与基线对比
# 7. 如果有改善 → 保留;如果没有或变差 → 回滚
# === 建立基线 ===
# 网络基线
iperf3 -c TARGET -t 30 -i 5 --json > /tmp/baseline.json
# 连接状态基线
ss -s
# Total: 4230
# TCP: 3856 (estab 2340, closed 860, orphaned 12, timewait 654)
# 当前参数快照
sysctl -a 2>/dev/null | grep -E '^net\.(ipv4\.tcp|core\.(r|w|somaxconn|netdev))' \
| sort > /tmp/sysctl_baseline.txt
# 应用层性能基线
# 用实际业务请求测试(如 wrk/ab/vegeta)
wrk -t 4 -c 100 -d 30s http://localhost:8080/api/healthTCP 参数的分层体系
Linux TCP 参数分为三层,理解它们的优先级至关重要:
# 三层参数体系:
#
# 层级 1: 全局默认值(sysctl net.ipv4.tcp_*)
# ↓ 所有新连接的起始值
#
# 层级 2: 全局上限(sysctl net.core.*_max)
# ↓ 即使应用设置更大值也不能超过这个
#
# 层级 3: 应用层覆盖(setsockopt)
# ↓ 应用可以在层级 2 的范围内修改
#
# 关键约束:
# 实际值 = min(应用设置, 全局上限)
# 如果应用不设置 → 使用全局默认值
# 如果应用设置 > 全局上限 → 被截断到全局上限
# 举例:接收缓冲区
# net.ipv4.tcp_rmem = "4096 131072 6291456" ← 默认值(自动调优范围)
# net.core.rmem_max = 212992 ← 全局上限
# 应用: setsockopt(SO_RCVBUF, 1048576) ← 应用请求 1MB
#
# 实际值 = min(1048576 × 2, 212992) = 212992
# (注意:SO_RCVBUF 设置值会被内核翻倍,但仍受 rmem_max 限制)
#
# 自动调优(tcp_moderate_rcvbuf=1)时:
# 内核根据连接状态动态调整,范围是 tcp_rmem[0] 到 tcp_rmem[2]
# 但如果应用调用了 setsockopt(SO_RCVBUF), 自动调优会被禁用!二、缓冲区参数
接收缓冲区(rmem)
# === 核心参数 ===
# TCP 接收缓冲区的自动调优范围
sysctl net.ipv4.tcp_rmem
# 4096 131072 6291456
# 最小值 默认值 最大值
# 4KB 128KB 6MB
# 全局接收缓冲区上限(包括非 TCP socket)
sysctl net.core.rmem_max
# 212992 (208KB) ← 很多发行版的默认值太小!
# 自动调优开关
sysctl net.ipv4.tcp_moderate_rcvbuf
# 1 = 开启(默认,推荐保持)
# === 参数含义 ===
# tcp_rmem[0] (min): 即使系统内存压力大,每条连接也保证的最小缓冲区
# tcp_rmem[1] (default): 新连接的初始缓冲区大小
# tcp_rmem[2] (max): 自动调优允许的最大缓冲区(受 rmem_max 约束)
# === 常见问题 ===
# 问题 1: tcp_rmem[2] 设了 16MB, 但实际缓冲区不超过 208KB
# 原因: rmem_max 只有 208KB,它是硬上限
# 修复:
sysctl -w net.core.rmem_max=16777216
# 问题 2: 应用调了 SO_RCVBUF 后性能反而下降
# 原因: setsockopt(SO_RCVBUF) 禁用了自动调优
# 自动调优能根据连接状态动态调整缓冲区大小
# 手动设置了固定值 → 可能太大浪费内存,也可能太小限制吞吐
# 建议: 除非确切知道目标值,否则不要手动设置 SO_RCVBUF
# 问题 3: 高并发场景下 TCP 内存不足
# 诊断:
cat /proc/net/sockstat | grep TCP
# TCP: inuse 12340 orphan 45 tw 3456 alloc 12400 mem 245678
# mem 是当前 TCP 使用的页面数(每页 4KB)
# 245678 × 4KB ≈ 960MB
sysctl net.ipv4.tcp_mem
# 383040 510720 766080
# 低水位 压力线 高水位(单位:页面,即 ×4KB)
# 1.46GB 1.95GB 2.92GB
# 当 mem > 压力线(510720) → 内核开始限制缓冲区增长
# 当 mem > 高水位(766080) → 内核拒绝分配新缓冲区发送缓冲区(wmem)
# TCP 发送缓冲区参数(与接收缓冲区对称)
sysctl net.ipv4.tcp_wmem
# 4096 16384 4194304
# 最小值 默认值 最大值
sysctl net.core.wmem_max
# 212992
# 发送缓冲区的特殊性:
# 1. 发送缓冲区中的数据在 ACK 之前不能释放
# → 高延迟链路需要更大的发送缓冲区
# 2. 发送缓冲区满时,write() 会阻塞(阻塞 socket)或返回 EAGAIN(非阻塞 socket)
# 3. 发送缓冲区大小影响 TCP 的发送能力
# → 如果缓冲区 < cwnd × MSS, 缓冲区成为瓶颈
# === 不同场景的推荐配置 ===
# 数据中心内部(RTT < 1ms, 10Gbps)
sysctl -w net.ipv4.tcp_rmem="4096 131072 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 131072 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
# 面向互联网的 Web 服务器(RTT 变化大)
sysctl -w net.ipv4.tcp_rmem="4096 131072 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
# 跨洲际高 BDP 链路(RTT 100-300ms, 1Gbps+)
sysctl -w net.ipv4.tcp_rmem="4096 524288 67108864"
sysctl -w net.ipv4.tcp_wmem="4096 524288 67108864"
sysctl -w net.core.rmem_max=67108864
sysctl -w net.core.wmem_max=67108864三、连接队列参数
SYN 队列和 Accept 队列
TCP 连接建立涉及两个队列,它们的溢出是最常见的连接问题之一:
flowchart LR
C["客户端 SYN"] --> SQ["SYN Queue<br>(半连接队列)<br>tcp_max_syn_backlog"]
SQ -->|"三次握手完成"| AQ["Accept Queue<br>(全连接队列)<br>min(somaxconn, backlog)"]
AQ -->|"accept()"| APP["应用进程"]
SQ -.->|"溢出: SYN 被丢弃<br>或启用 SYN Cookie"| DROP1["丢弃/Cookie"]
AQ -.->|"溢出: 取决于<br>tcp_abort_on_overflow"| DROP2["丢弃 ACK/RST"]
# === SYN 队列(半连接队列) ===
sysctl net.ipv4.tcp_max_syn_backlog
# 128 (很多发行版的默认值太小!)
# SYN 队列存储还没完成三次握手的连接
# 每个连接只需要少量内存(~几百字节)
# 在高并发短连接场景下容易溢出
# 增大 SYN 队列
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# SYN 队列溢出的检测
netstat -s | grep "SYNs to LISTEN"
# 或
cat /proc/net/netstat | grep -oP 'ListenOverflows \K[0-9]+'
cat /proc/net/netstat | grep -oP 'ListenDrops \K[0-9]+'
# 这两个计数器持续增长 → SYN/Accept 队列溢出
# SYN Cookie(队列溢出时的保护机制)
sysctl net.ipv4.tcp_syncookies
# 1 = 开启(默认,推荐保持)
# SYN Cookie 在 SYN 队列满时不分配资源
# 而是把状态编码在 SYN+ACK 的序列号中
# 代价:无法使用 TCP 选项(窗口缩放、SACK 等)
# === Accept 队列(全连接队列) ===
sysctl net.core.somaxconn
# 128 → 推荐增大到 4096 或更大
# Accept 队列大小 = min(somaxconn, listen() 的 backlog 参数)
# 所以修改 somaxconn 后,应用的 listen() 也要配合
# 各服务的 backlog 配置:
# Nginx: listen 80 backlog=4096;
# Redis: tcp-backlog 511
# Go: net.Listen() 内部使用 sysctl 值
# Java: ServerSocket(port, backlog)
# Accept 队列溢出行为
sysctl net.ipv4.tcp_abort_on_overflow
# 0(默认): 丢弃最后的 ACK,客户端会重试
# 1: 发送 RST 立即拒绝连接
# 推荐保持 0,给服务器更多恢复时间
# === 监控队列状态 ===
# 用 ss 查看队列使用情况
ss -ltn
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 4096 *:80 *:*
#
# 对 LISTEN 状态:
# Recv-Q = 当前 Accept 队列中的连接数
# Send-Q = Accept 队列容量(即 min(somaxconn, backlog))
# 如果 Recv-Q 接近 Send-Q → 队列快满了
# 持续监控
watch -n 1 'ss -ltn | head -20'四、TIME_WAIT 参数
TIME_WAIT 的工程控制
# TIME_WAIT 状态持续 2 × MSL (Maximum Segment Lifetime)
# Linux 中 MSL = 60 秒, 所以 TIME_WAIT = 120 秒(硬编码)
# 查看 TIME_WAIT 连接数
ss -s | grep timewait
# timewait: 12345
# 大量 TIME_WAIT 的典型场景:
# 1. 高频短连接(如每秒数千个 HTTP 请求到后端)
# 2. 主动关闭连接的一方会进入 TIME_WAIT
# 3. 作为客户端连接后端服务时尤为常见
# === 控制参数 ===
# tcp_tw_reuse: 允许复用 TIME_WAIT 连接的端口
sysctl net.ipv4.tcp_tw_reuse
# 0: 禁用
# 1: 作为客户端时允许复用 TIME_WAIT 的端口
# 2: 同时对回环地址和外部地址启用
# 推荐设置
sysctl -w net.ipv4.tcp_tw_reuse=1
# 安全条件:需要 tcp_timestamps=1(默认已开启)
# 原理:通过时间戳区分旧连接和新连接的包
# tcp_max_tw_buckets: TIME_WAIT 连接数上限
sysctl net.ipv4.tcp_max_tw_buckets
# 默认通常是 32768 或 65536
# 超过此值的 TIME_WAIT 连接会被立即销毁
# 这是一个安全阀,不推荐随意增大
# ⚠️ tcp_tw_recycle: 已在 Linux 4.12 中移除!
# 这个参数在 NAT 环境下会导致连接问题
# 因为它基于 per-IP 的时间戳判断,NAT 后的多台机器时间戳不同
# 如果你的内核版本 < 4.12 且开启了此参数 → 立即关闭
# sysctl -w net.ipv4.tcp_tw_recycle=0
# === 更好的解决方案:连接池 ===
# 与其调 TIME_WAIT 参数,不如用连接池避免频繁创建/销毁连接
# HTTP: Keep-Alive(HTTP/1.1 默认开启)
# 数据库: 连接池(HikariCP, pgbouncer)
# gRPC: 长连接复用
# Redis: 连接池(Lettuce, Jedis)五、Keepalive 参数
TCP Keepalive 的三个参数
# TCP Keepalive 用于检测"死连接"(对端崩溃/网络中断)
# 空闲多久后开始探测
sysctl net.ipv4.tcp_keepalive_time
# 7200(2 小时!默认值太长)
# 探测间隔
sysctl net.ipv4.tcp_keepalive_intvl
# 75(秒)
# 探测次数
sysctl net.ipv4.tcp_keepalive_probes
# 9
# 默认行为:
# 连接空闲 2 小时后开始探测
# 每 75 秒探测一次
# 连续 9 次失败才判定连接死亡
# 总检测时间:7200 + 75 × 9 = 7875 秒 ≈ 2 小时 11 分钟
# 对于大多数应用来说太慢了
# === 推荐配置 ===
# 通用 Web 服务
sysctl -w net.ipv4.tcp_keepalive_time=600 # 10 分钟
sysctl -w net.ipv4.tcp_keepalive_intvl=15 # 15 秒
sysctl -w net.ipv4.tcp_keepalive_probes=5 # 5 次
# 检测时间:600 + 15 × 5 = 675 秒 ≈ 11 分钟
# 内部微服务
sysctl -w net.ipv4.tcp_keepalive_time=60 # 1 分钟
sysctl -w net.ipv4.tcp_keepalive_intvl=10 # 10 秒
sysctl -w net.ipv4.tcp_keepalive_probes=3 # 3 次
# 检测时间:60 + 10 × 3 = 90 秒
# === 应用层控制 ===
# 应用可以通过 setsockopt 覆盖系统设置
# 开启 Keepalive(默认是关的!)
# setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
# 设置每个 socket 的 Keepalive 参数
# setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &time, sizeof(time));
# setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
# setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &probes, sizeof(probes));
# === Keepalive 与 NAT 的交互 ===
# 很多 NAT 设备(路由器、云安全组)有连接超时
# AWS Security Group: 默认 350 秒
# 家用路由器: 通常 120-300 秒
# 如果 Keepalive 间隔 > NAT 超时
# → NAT 表项被清理 → 连接断开 → RST
# 所以 tcp_keepalive_time 不要超过 NAT 超时的 1/2六、重传与超时参数
# === SYN 重传 ===
sysctl net.ipv4.tcp_syn_retries
# 6(默认)
# SYN 的重传间隔是指数退避:1s, 2s, 4s, 8s, 16s, 32s
# 总等待时间:1 + 2 + 4 + 8 + 16 + 32 = 63 秒
# 推荐:设为 2 或 3,减少连接建立超时时间
sysctl -w net.ipv4.tcp_syn_retries=3
# 总等待时间:1 + 2 + 4 = 7 秒
# SYN+ACK 重传(服务端)
sysctl net.ipv4.tcp_synack_retries
# 5(默认)
sysctl -w net.ipv4.tcp_synack_retries=3
# === 数据重传 ===
sysctl net.ipv4.tcp_retries1
# 3(默认)
# 重传 3 次后通知网络层"可能有问题"
# 触发 PMTUD、路由更新等
sysctl net.ipv4.tcp_retries2
# 15(默认)
# 重传 15 次后放弃连接
# 实际超时时间取决于 RTO,通常在 13-30 分钟之间
# 对于大多数场景太长
sysctl -w net.ipv4.tcp_retries2=8
# 总超时时间 ≈ 100-200 秒,更合理
# === Orphan 连接 ===
# Orphan = 应用已关闭但内核还没完成清理的连接
sysctl net.ipv4.tcp_max_orphans
# 默认通常 16384-65536
# 超过此值的 orphan 连接会被立即 RST
sysctl net.ipv4.tcp_orphan_retries
# 0(默认,实际按 tcp_retries2/2 计算)
# 控制 orphan 连接的 FIN 重传次数
# === conntrack 连接追踪(负载均衡/NAT 节点必须调优) ===
# conntrack 表大小
sysctl net.nf_conntrack_max
# 默认通常 65536
# 每条连接约 ~300 字节内存
# 高并发 NAT/LB 场景需要增大
sysctl -w net.nf_conntrack_max=1048576
# 1M 条连接 ≈ 300MB 内存,对于 LB 是合理的
# 连接追踪的各状态超时
sysctl net.netfilter.nf_conntrack_tcp_timeout_established
# 432000 (5 天!) → 太长
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=7200
sysctl net.netfilter.nf_conntrack_tcp_timeout_time_wait
# 120
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
# 检查 conntrack 表使用情况
conntrack -C # 当前条目数
# 或
cat /proc/sys/net/netfilter/nf_conntrack_count
# conntrack 表满时会丢包!内核日志:
# nf_conntrack: table full, dropping packet
# 这是 K8s/LB 环境中常见的丢包原因
dmesg | grep conntrack七、TCP 选项参数
# === 时间戳 ===
sysctl net.ipv4.tcp_timestamps
# 1(默认,推荐保持开启)
# 用途:
# 1. RTT 精确测量(RTTM)
# 2. PAWS(序列号回绕保护)
# 3. tcp_tw_reuse 的前提条件
# 只有在中间设备剥离时间戳选项时才需要关闭
# === 窗口缩放 ===
sysctl net.ipv4.tcp_window_scaling
# 1(默认,必须保持开启)
# 没有窗口缩放,最大窗口 = 64KB
# 对于任何现代网络都远远不够
# === SACK ===
sysctl net.ipv4.tcp_sack
# 1(默认,推荐保持开启)
# SACK 允许接收方告诉发送方具体哪些段丢了
# 大幅减少不必要的重传
# === TCP Fast Open ===
sysctl net.ipv4.tcp_fastopen
# 0: 禁用
# 1: 作为客户端启用
# 2: 作为服务端启用
# 3: 同时启用(推荐用于 Web 服务)
sysctl -w net.ipv4.tcp_fastopen=3
# TFO 允许在 SYN 包中携带数据
# 减少 1 个 RTT 的握手延迟
# 需要客户端和服务端都支持
# === ECN ===
sysctl net.ipv4.tcp_ecn
# 0: 禁用
# 1: 启用(主动请求)
# 2: 被动支持(默认,推荐)
# ECN 允许路由器在不丢包的情况下通知拥塞八、socket 选项速查
常用 socket 选项对照表
| 选项 | 层级 | 作用 | 推荐用法 |
|---|---|---|---|
| SO_REUSEADDR | SOL_SOCKET | 允许绑定处于 TIME_WAIT 的地址 | 服务端必开 |
| SO_REUSEPORT | SOL_SOCKET | 允许多进程绑定同一端口 | 多进程架构 |
| SO_KEEPALIVE | SOL_SOCKET | 启用 TCP Keepalive | 长连接必开 |
| SO_RCVBUF | SOL_SOCKET | 设置接收缓冲区大小 | 慎用,会禁用自动调优 |
| SO_SNDBUF | SOL_SOCKET | 设置发送缓冲区大小 | 同上 |
| TCP_NODELAY | IPPROTO_TCP | 关闭 Nagle 算法 | 延迟敏感场景 |
| TCP_CORK | IPPROTO_TCP | 延迟发送直到取消 Cork | 批量写入 |
| TCP_KEEPIDLE | IPPROTO_TCP | Keepalive 空闲时间 | 按需调整 |
| TCP_KEEPINTVL | IPPROTO_TCP | Keepalive 探测间隔 | 按需调整 |
| TCP_KEEPCNT | IPPROTO_TCP | Keepalive 探测次数 | 按需调整 |
| TCP_CONGESTION | IPPROTO_TCP | 拥塞控制算法 | 按连接级别选择 |
| TCP_FASTOPEN | IPPROTO_TCP | TCP Fast Open | Web 服务 |
| TCP_USER_TIMEOUT | IPPROTO_TCP | 用户数据超时时间 | 替代 tcp_retries2 |
TCP_USER_TIMEOUT:精确控制超时
# TCP_USER_TIMEOUT 是一个经常被忽视但非常有用的选项
# 它定义了:发送数据后,如果在指定时间内没有收到 ACK,断开连接
# 与 tcp_retries2 的区别:
# tcp_retries2 控制的是重传次数,超时时间不精确(依赖 RTO 退避)
# TCP_USER_TIMEOUT 直接设置毫秒级超时,更精确
# C:
# unsigned int timeout = 30000; // 30 秒
# setsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
# Go (1.20+):
# conn.(*net.TCPConn).SetOption(unix.TCP_USER_TIMEOUT, 30000)
# 推荐用法:
# 替代 tcp_retries2 实现可预测的超时行为
# 特别适合微服务间的 RPC 调用
# 设置值应略大于应用层超时(避免应用超时前连接已被内核断开)九、场景化调优模板
高并发 Web 服务器
# 特征:大量短连接、高并发、混合请求大小
# 目标:最大化连接处理能力、减少延迟
cat > /etc/sysctl.d/99-web-server.conf << 'EOF'
# --- 连接队列 ---
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_abort_on_overflow = 0
# --- 缓冲区 ---
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_moderate_rcvbuf = 1
# --- TIME_WAIT ---
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 65536
# --- 连接超时 ---
net.ipv4.tcp_syn_retries = 3
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_retries2 = 8
net.ipv4.tcp_fin_timeout = 15
# --- Keepalive ---
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
# --- 性能 ---
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
# --- 拥塞控制 ---
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# --- 端口范围 ---
net.ipv4.ip_local_port_range = 1024 65535
EOF
sysctl --system数据库服务器
# 特征:少量长连接、大数据传输、延迟敏感
# 目标:稳定低延迟、高吞吐量
cat > /etc/sysctl.d/99-db-server.conf << 'EOF'
# --- 缓冲区(数据库传输量大) ---
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 262144 67108864
net.ipv4.tcp_wmem = 4096 262144 67108864
# --- 连接队列(数据库连接数通常不多) ---
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
# --- Keepalive(长连接需要快速检测死连接) ---
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
# --- 超时(数据库不容许长时间无响应) ---
net.ipv4.tcp_retries2 = 5
# --- TCP 选项 ---
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
# --- 拥塞控制 ---
net.ipv4.tcp_congestion_control = cubic
EOF跨洲际数据传输
# 特征:高 BDP、高延迟、需要最大化带宽利用
# 目标:充分利用链路带宽
cat > /etc/sysctl.d/99-wan-transfer.conf << 'EOF'
# --- 大缓冲区(BDP 可能达到几十 MB) ---
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 1048576 134217728
net.ipv4.tcp_wmem = 4096 1048576 134217728
# --- BBR 更适合高 BDP 和 Bufferbloat 环境 ---
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# --- TCP 选项必须全开 ---
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
# --- 增大 initcwnd(减少慢启动时间) ---
# 需要通过 ip route 设置,不能在 sysctl 中配置
# ip route change default via GW dev eth0 initcwnd 20
EOF十、基准测试与验证
验证调优效果的方法
# === 带宽测试 ===
# iperf3 基本测试
iperf3 -c TARGET -t 30 -i 5
# 多流并行测试(模拟多连接场景)
iperf3 -c TARGET -t 30 -P 4
# 反向测试(测接收方向)
iperf3 -c TARGET -t 30 -R
# === 延迟测试 ===
# TCP 连接延迟(SYN→SYN+ACK 的 RTT)
hping3 -S -p 80 -c 20 TARGET
# 应用层延迟
curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" -o /dev/null -s https://TARGET
# === 连接速率测试 ===
# 用 wrk 测试 HTTP 连接建立速率
wrk -t 4 -c 100 -d 30s http://TARGET:80/
# === 调优前后对比 ===
# 修改参数前
iperf3 -c TARGET -t 30 --json > /tmp/before.json
# 修改参数
sysctl -w net.ipv4.tcp_rmem="4096 131072 16777216"
sysctl -w net.core.rmem_max=16777216
# 修改参数后(等待旧连接断开)
sleep 5
iperf3 -c TARGET -t 30 --json > /tmp/after.json
# 对比
echo "Before:"
jq '.end.sum_sent.bits_per_second / 1e6' /tmp/before.json
echo "After:"
jq '.end.sum_sent.bits_per_second / 1e6' /tmp/after.json检查参数是否生效
# 验证参数已生效
sysctl net.ipv4.tcp_rmem net.core.rmem_max
# 验证实际连接使用了新参数
ss -tim dst TARGET | head -20
# 查看 rcv_space 是否增大
# 验证拥塞控制算法
ss -ti | grep -oP '^\S+' | sort | uniq -c | sort -rn
# 2340 cubic
# 156 bbr
# 旧连接仍然使用旧算法
# 验证 FQ 调度器
tc qdisc show dev eth0 | grep fq
# 验证 TCP Fast Open
cat /proc/sys/net/ipv4/tcp_fastopen
# 3 = 客户端 + 服务端都启用十一、案例:微服务间 P99 延迟优化
# 背景:
# 微服务架构,Go 编写,服务间 gRPC 通信
# 问题:某服务的 P99 延迟从 5ms 突然升高到 200-500ms
# === 第 1 步:排查应用层 ===
# gRPC 的 channelz 显示连接正常
# 链路追踪显示延迟主要在网络传输层
# === 第 2 步:检查网络状态 ===
ss -ti dst SERVICE_B_IP | head -5
# cubic wscale:7,7 rto:204 rtt:0.5/0.1 mss:1448
# cwnd:10 ssthresh:7 bytes_sent:15684400
# bytes_retrans:14480 retrans:0/10
# ssthresh:7 说明发生过丢包
# bytes_retrans > 0 确认有重传
# cwnd:10 看起来正常
# === 第 3 步:检查 Nagle + Delayed ACK 问题 ===
# gRPC 应该设置了 TCP_NODELAY...
strace -e trace=setsockopt -p PID 2>&1 | grep TCP_NODELAY
# 没有输出 → Go 的 gRPC 没有设置 TCP_NODELAY!
# Go 的 net.Dial 默认开启 Nagle
# gRPC-Go 通常通过 grpc.WithContextDialer 设置
# 检查代码发现使用了自定义 dialer 但忘了设 NoDelay
# === 第 4 步:检查 Keepalive 问题 ===
ss -to dst SERVICE_B_IP | head -3
# timer:(keepalive,7135.240ms,0)
# Keepalive 定时器 = 7200s - 65s ≈ 已等待 65 秒
# 默认 Keepalive 时间 2 小时,太长了
# 中间的 K8s Service (iptables/IPVS) 超时时间是 300 秒
# 如果 Keepalive > 300s → NAT 表项被清理 → RST
# === 第 5 步:修复 ===
# a) 应用层修复
# Go gRPC 服务端和客户端都设置 Keepalive
# grpc.KeepaliveParams{Time: 60 * time.Second}
# 确保 TCP_NODELAY 被设置
# b) 系统层调优
sysctl -w net.ipv4.tcp_keepalive_time=60
sysctl -w net.ipv4.tcp_keepalive_intvl=10
sysctl -w net.ipv4.tcp_keepalive_probes=3
# c) 结果
# P99 延迟从 200-500ms 降回 5ms
# 根因:Nagle + Delayed ACK 的 40ms 延迟交互
# 加上偶发的 NAT 超时导致的连接重建十二、参数速查表
高频使用的 sysctl 参数
| 参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| net.core.somaxconn | 128 | 65535 | Accept 队列上限 |
| net.ipv4.tcp_max_syn_backlog | 128 | 65535 | SYN 队列上限 |
| net.core.rmem_max | 212992 | 16777216 | 接收缓冲区硬上限 |
| net.core.wmem_max | 212992 | 16777216 | 发送缓冲区硬上限 |
| net.ipv4.tcp_rmem | 4096 131072 6291456 | 见场景 | 接收缓冲区范围 |
| net.ipv4.tcp_wmem | 4096 16384 4194304 | 见场景 | 发送缓冲区范围 |
| net.ipv4.tcp_tw_reuse | 0 | 1 | 复用 TIME_WAIT 端口 |
| net.ipv4.tcp_keepalive_time | 7200 | 60-600 | Keepalive 空闲时间 |
| net.ipv4.tcp_syn_retries | 6 | 2-3 | SYN 重传次数 |
| net.ipv4.tcp_retries2 | 15 | 5-8 | 数据重传次数 |
| net.ipv4.tcp_fin_timeout | 60 | 15-30 | FIN_WAIT_2 超时 |
| net.ipv4.tcp_fastopen | 0 | 3 | TCP Fast Open |
| net.ipv4.tcp_congestion_control | cubic | cubic/bbr | 拥塞控制算法 |
| net.ipv4.ip_local_port_range | 32768 60999 | 1024 65535 | 客户端端口范围 |
十三、结论
TCP 调优不是一个”照着最佳实践抄参数”的过程——不同场景需要不同的配置,而且参数之间有复杂的依赖关系。
几个核心原则:
先测量,再调优。 没有基线数据的调优就是猜。用
ss -ti、iperf3、wrk建立基线,修改后用相同方法验证。理解三层约束。
sysctl参数设置默认值,*_max参数设置上限,应用层setsockopt在上限范围内覆盖。改tcp_rmem别忘了rmem_max。不要手动设 SO_RCVBUF/SO_SNDBUF。 除非你确切知道需要多少。手动设置会禁用 Linux 的自动调优——大多数情况下内核比你调得更好。
连接池优于 TIME_WAIT 参数。 调
tcp_tw_reuse是对症治疗。用连接池(HTTP Keep-Alive、数据库连接池)避免频繁创建/销毁连接才是治本。Keepalive 要比 NAT 超时短。 忘记这一条会导致间歇性的连接断开——最难排查的那种 bug。
TCP 调优解决的是”怎么调”的问题。下一篇我们来讲”怎么查”——TCP 问题诊断实战,涵盖重传、RST 和窗口异常的系统化排查方法。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】内核网络参数调优:sysctl 全景与实战
Linux 内核网络参数是系统网络性能的基础旋钮。本文从 /proc/sys/net/ 的参数体系出发,系统讲解收发缓冲区自动调优、TCP Backlog 队列、conntrack 连接追踪表、SYN Flood 防护参数、TIME_WAIT 管理,以及参数调优的系统化方法论——先基准、再调整、后验证。
【Linux 网络子系统深度拆解】内核网络调优方法论:从基准测试到生产验证
系统化的 Linux 内核网络调优方法论:从基准测试建立性能基线,到 sysctl 参数与内核数据结构的对应关系,再到中断亲和性、NUMA 拓扑、ring buffer、qdisc 的逐层调优,最终通过 A/B 对比验证生产效果。
【网络工程】Socket 编程模型演进:从阻塞到多路复用
网络编程模型的选择决定了服务的并发能力上限。本文从阻塞 I/O 到非阻塞、select、poll、epoll,逐步解剖每种模型的系统调用开销、性能边界与适用场景,用 C 代码实测从 C10K 到 C1M 的演进。
【网络工程】零拷贝网络:sendfile、splice 与 MSG_ZEROCOPY
数据从磁盘到网卡的传统路径涉及 4 次拷贝和多次上下文切换。本文系统剖析 sendfile、splice、vmsplice、MSG_ZEROCOPY 四种零拷贝技术的内核实现、适用场景与性能差异,并以 Kafka 和 Nginx 为案例分析零拷贝在生产系统中的工程实践。