2、TCP三次握手优化:调整tcp_syn_retries、tcp_synack_retries、tcp_retries1/2,减少握手延迟

TCP三次握手,说白了就是客户端和服务端互相确认「我在,你也在,咱们能聊」。这个流程本身不复杂,但一旦网络有波动,重传策略没配好,握手延迟就能从几毫秒飙升到几十秒。

我见过不少线上事故,根源就是握手超时太长。用户点一下按钮,转圈转了半分钟,最后超时报错。嗯,今天咱们就把这几个关键参数掰开揉碎,看看怎么调能既快又稳。

3.1 三次握手的重传机制

先回顾一下标准流程:客户端发 SYN,服务端回 SYN+ACK,客户端再回 ACK。任何一步丢了,发送方都会等一个超时时间,然后重传。

这个超时时间不是固定的。Linux 内核用指数退避算法,每次重传间隔翻倍。初始 RTO(Retransmission Timeout)默认 1 秒,第二次 2 秒,第三次 4 秒……直到达到最大重传次数。

为什么会这样设计?因为网络拥塞时,频繁重传只会雪上加霜。但问题来了——在内部数据中心或者云上专线环境,网络质量很好,1 秒的初始超时就太保守了。

核心思路:根据网络环境,降低重传次数或缩短超时时间,让握手失败时快速失败,而不是傻等。

3.2 四个关键参数详解

Linux 提供了四个 sysctl 参数来控制重传行为。我习惯把它们分成两组:握手阶段和数据传输阶段。

参数 默认值 作用阶段 含义
net.ipv4.tcp_syn_retries 6 客户端发 SYN 客户端重传 SYN 的最大次数
net.ipv4.tcp_synack_retries 5 服务端发 SYN+ACK 服务端重传 SYN+ACK 的最大次数
net.ipv4.tcp_retries1 3 数据传输 放弃前尝试重传的次数(通知上层)
net.ipv4.tcp_retries2 15 数据传输 彻底断开前尝试重传的次数

tcp_syn_retries:客户端别死磕

这个参数控制客户端发 SYN 后,收不到 SYN+ACK 时最多重试几次。默认 6 次,算下来总超时时间大约 1+2+4+8+16+32 = 63 秒。你想想看,用户等一分钟才提示「连接失败」,这体验能好吗?

我在项目中遇到过,移动端 App 访问后端 API,弱网环境下经常卡死。后来把 tcp_syn_retries 从 6 降到 3,总超时缩到 7 秒左右。用户反馈明显改善——要么快速连上,要么快速报错,不再傻等。

建议:内部服务之间设为 2-3,面向公网用户设为 3-4。别设成 1,偶尔一次丢包就断连,太脆弱了。

tcp_synack_retries:服务端别太执着

服务端收到 SYN 后回复 SYN+ACK,如果客户端没回 ACK,服务端就会重传。默认 5 次,总超时约 31 秒。

这个参数主要影响服务端的资源占用。每个半连接(SYN_RCVD 状态)都要占内存,重传次数越多,积压的半连接越多,容易被 SYN Flood 攻击拖垮。

我曾经帮一个电商团队排查过,大促期间服务器负载飙升,一看 tcp_synack_retries 还是默认 5。降到 2 之后,半连接队列压力骤降,正常请求反而处理得更快了。

注意:别把 tcp_synack_retries 设成 0。如果客户端确实存在但 ACK 丢了,服务端直接放弃,会导致正常连接失败。我建议至少保留 1-2 次重试。

tcp_retries1 和 tcp_retries2:数据传输阶段的保底

这两个参数控制连接建立后,数据包重传的行为。

  • tcp_retries1(默认 3):重传达到这个次数后,内核会通知上层(应用层)网络可能有问题。但连接不会断,继续重传。
  • tcp_retries2(默认 15):重传达到这个次数后,内核直接断开连接。默认 15 次,总超时大约 15-30 分钟。

15 次重传,半小时才断连?这在很多场景下是不可接受的。比如实时交易系统,一个连接卡住半小时,资源一直被占用,其他请求进不来。

我个人习惯把 tcp_retries2 降到 5-8,对应超时大约 30-90 秒。既给了网络抖动恢复的时间,又不会无限等下去。

经验值参考:

  • 同机房内网:tcp_retries2 = 3(约 15 秒断开)
  • 同城跨机房:tcp_retries2 = 5(约 30 秒断开)
  • 跨地域公网:tcp_retries2 = 8(约 90 秒断开)

3.3 参数调整的完整流程

调整这些参数,我建议按以下步骤来:

  1. 评估网络环境:内网还是公网?丢包率多少?RTT 多少?
  2. 确定目标:要快速失败还是尽量保连接?
  3. 计算超时时间:用公式 初始 RTO × (2^n - 1) 估算总超时。
  4. 修改 sysctl 配置:写入 /etc/sysctl.conf 或 /etc/sysctl.d/ 下的文件。
  5. 验证效果:用 ss -ti 或抓包工具确认重传次数变化。

下面是一个典型的配置示例:

# /etc/sysctl.d/99-tcp-optimize.conf

# 客户端 SYN 重传:3 次,约 7 秒超时
net.ipv4.tcp_syn_retries = 3

# 服务端 SYN+ACK 重传:2 次,约 3 秒超时
net.ipv4.tcp_synack_retries = 2

# 数据传输:重传 5 次后通知上层,8 次后断开
net.ipv4.tcp_retries1 = 5
net.ipv4.tcp_retries2 = 8

改完后执行 sysctl -p /etc/sysctl.d/99-tcp-optimize.conf 立即生效。

3.4 避坑指南

调这些参数有几个容易踩的坑,我一个个说:

  • 别一刀切:所有服务用同一套配置。我曾经见过一个团队把 tcp_retries2 统一设成 3,结果跨地域的备份任务频繁断连。不同场景要区别对待。
  • 注意内核版本差异:老版本内核(3.x 以下)的 RTO 计算方式不同,实际超时时间可能比预期长。升级内核或实测验证。
  • 配合 tcp_syncookies 使用:如果调低了 tcp_synack_retries,建议开启 tcp_syncookies=1,防止半连接队列溢出。
  • 监控重传率:调低参数后,如果重传率飙升,说明网络确实不好,调低反而导致大量连接失败。这时候应该先修网络,而不是调参数。

快速验证命令:

ss -ti | grep -E "retrans|rto" 可以查看当前连接的重传次数和 RTO 值。

nstat -az | grep TcpRetrans 可以查看系统级重传统计。

3.5 知识体系总览

下面这张图把三次握手优化涉及的核心逻辑串起来了。从参数到效果,一目了然。

TCP 三次握手优化知识体系 TCP 三次握手 握手阶段重传 tcp_syn_retries(客户端) tcp_synack_retries(服务端) 数据传输重传 tcp_retries1(通知上层) tcp_retries2(断开连接) 优化效果 减少握手延迟 · 快速失败 · 释放资源 · 提升用户体验 注意事项 不同网络环境区别对待 · 配合 tcp_syncookies · 监控重传率

3.6 总结

TCP 三次握手的优化,核心就一句话:根据网络质量,合理设置重传次数。网络好就少等,网络差就多等,但别无限等。

我个人的经验是,先拿非核心业务做灰度,观察一周的重传率和连接成功率,再逐步推广到全量。别一上来就改所有服务器,万一出问题,回滚都来不及。

嗯,这几个参数调好了,用户感知到的就是「点一下就连上了」,而不是「转圈圈然后报错」。这就是性能优化的价值所在。