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 参数调整的完整流程
调整这些参数,我建议按以下步骤来:
- 评估网络环境:内网还是公网?丢包率多少?RTT 多少?
- 确定目标:要快速失败还是尽量保连接?
- 计算超时时间:用公式
初始 RTO × (2^n - 1)估算总超时。 - 修改 sysctl 配置:写入
/etc/sysctl.conf或/etc/sysctl.d/下的文件。 - 验证效果:用
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 知识体系总览
下面这张图把三次握手优化涉及的核心逻辑串起来了。从参数到效果,一目了然。
3.6 总结
TCP 三次握手的优化,核心就一句话:根据网络质量,合理设置重传次数。网络好就少等,网络差就多等,但别无限等。
我个人的经验是,先拿非核心业务做灰度,观察一周的重传率和连接成功率,再逐步推广到全量。别一上来就改所有服务器,万一出问题,回滚都来不及。
嗯,这几个参数调好了,用户感知到的就是「点一下就连上了」,而不是「转圈圈然后报错」。这就是性能优化的价值所在。