前事不忘,后事之师,不忘国耻!

 用户注册  找回密码
 用户注册
搜索
查看: 25|回复: 0

数据库主机网络连接异常排查实战:从 SYN 丢弃到内核队列调优

[复制链接]

数据库主机网络连接异常排查实战:从 SYN 丢弃到内核队列调优

[复制链接]
dbaai

主题

0

回帖

344

积分

DBAAI

积分
344
昨天 07:05 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?用户注册

×
数据库主机网络连接异常排查实战:从 SYN 丢弃到内核队列调优


一、具体的问题

某天上午十点,业务方连续报障:应用日志里 Connection timed out 频发,连接池里堆积了上百个等建连的线程。第一反应往往是去看数据库负载——结果一查,MySQL 的 CPU、IO、Threads_running 全部平稳,show processlist 里干干净净。更蹊跷的是:报错集中在十点整到十点零三分这三分多钟,之后自愈,且报错的是新建连接,已建立的存量连接查询一切正常。

"新建连接超时、存量连接正常",这个特征组合基本可以把嫌疑从数据库负载上移开,指向主机的内核网络栈:建连的第一步(TCP 三次握手)卡住了,而握手之后的数据交互根本还没开始。如果这时候你选择重启数据库、加大连接数、重试——大概率是无效的,因为问题压根不在那一层。本文用一次真实的排查,把这类问题的定位路径和内核调优一次讲透。

二、核心原理

理解这类问题,只需要记住三次握手中的两个队列:


  • 半连接队列(SYN 队列):服务端收到 SYN、回复 SYN-ACK 后,连接进入半连接队列等待第三次 ACK。由 net.ipv4.tcp_max_syn_backlog 控制。
  • 全连接队列(accept 队列):完成握手、等待应用调用 accept() 取走的连接。大小取两个值的较小者:应用传入的 listen backlog 与内核的 net.core.somaxconn。


关键结论:队列满了之后,内核的默认行为是丢弃——新到的 SYN 或最后那个 ACK 被静默丢掉,客户端只能靠超时或重传慢慢熬,表现就是"连接超时"。队列为什么会满?最常见的原因不是流量太大,而是应用 accept 得太慢:数据库进程被某个大事务/导出任务占住了线程、或者突发连接风暴在几百毫秒内打进来。也就是说,网络参数调优是扩容队列的兜底手段,根因常常在应用侧的 accept 能力。

另外三个高频元凶:


  • TIME_WAIT 洪水:主动关闭方进入 TIME_WAIT 等待 2MSL(默认 60 秒),本身是正常状态。真正的问题是短连接高频建连把本地临时端口耗尽(ip_local_port_range 默认 32768~60999 只有约 2.8 万个),新建连接直接失败。
  • conntrack 表满:开了 iptables/conntrack 的机器,nf_conntrack_max 被打满后新连接被静默丢弃,dmesg 里能看到 nf_conntrack: table full, dropping packet。
  • Nagle 与延迟 ACK:小包高频写场景,发送端攒包 + 接收端延迟确认叠加,能造出 40~200ms 的诡异延迟,表现为"查询偶尔慢",而不是"连不上"。


三、实例参考(动手步骤)

步骤 0:确认现状基线
  1. # 事件时间点的连接概览
  2. ss -s
  3. # 监听套接字:Recv-Q 是当前 accept 队列长度,Send-Q 是队列上限
  4. ss -lnt | grep -E '3306|1521|5432'
  5. # 健康标准:Recv-Q 长期远小于 Send-Q;若 Recv-Q 反复顶到 Send-Q,就是 accept 队列满的实锤
复制代码

步骤 1:查内核丢包计数,锁定队列溢出
  1. # 半连接队列溢出 / accept 队列溢出的累计计数(重点看是否在增长)
  2. netstat -s | grep -iE 'listen|SYN'
  3. #   TCP request sockets discarded     <-- SYN 被丢(半连接溢出)
  4. #   TCP listen queue overflows        <-- accept 队列溢出
  5. #   TCP SYN cookies sent              <-- 已触发 syncookie 保护
  6. # 间隔 10 秒采两次,确认是"正在发生"而不是历史累计
  7. netstat -s | grep -iE 'overflow|discarded'; sleep 10; netstat -s | grep -iE 'overflow|discarded'
复制代码

如果 overflow 计数在增长,同时 ss -lnt 里 Recv-Q 顶满,结论就坐实了:握手完成了但应用没来得及 accept。

步骤 2:抓包验证握手在哪一步断的

在客户端(应用主机)抓包,比在服务端更能反映真实体验:
  1. # 客户端抓 3306 端口的握手过程
  2. tcpdump -i any -nn 'host <db_ip> and tcp port 3306' -c 50
  3. # 典型故障形态:客户端反复发 [SYN],服务端毫无响应(半连接被丢)
  4. # 或:三次握手成功后应用层无响应(accept 慢)
复制代码

再看重传统计佐证网络质量:
  1. netstat -s | grep -i retrans
  2. # TCPTimeouts / TCPSynRetransmits 持续增长,配合丢包计数,就是链路质量问题
复制代码

步骤 3:检查 conntrack 与临时端口两个静默杀手
  1. # conntrack:当前条目 vs 上限
  2. cat /proc/sys/net/netfilter/nf_conntrack_count
  3. cat /proc/sys/net/netfilter/nf_conntrack_max
  4. dmesg | grep -i 'table full'          # 出现过即中过招
  5. # 本地临时端口:短连接客户端重点看
  6. cat /proc/sys/net/ipv4/ip_local_port_range
  7. # 默认 32768 61000,约 2.8 万个端口;TIME_WAIT 60 秒内短连接超过这个量级就会耗尽
复制代码

步骤 4:内核与应用双侧调优(可照做)

写 /etc/sysctl.d/99-db-net.conf 并生效:
  1. cat >> /etc/sysctl.d/99-db-net.conf <<'EOF'
  2. # accept 队列上限:数据库主机建议拉到 4096 以上
  3. net.core.somaxconn = 4096
  4. # 半连接队列上限
  5. net.ipv4.tcp_max_syn_backlog = 4096
  6. # 诊断期临时打开:队列满时直接回 RST,让客户端立刻报错而不是傻等(定位完关掉)
  7. # net.ipv4.tcp_abort_on_overflow = 1
  8. # 客户端临时端口范围放宽
  9. net.ipv4.ip_local_port_range = 10240 65535
  10. # 仅对"主动连出去"的方向安全:TIME_WAIT 端口复用(NAT 环境慎用)
  11. net.ipv4.tcp_tw_reuse = 1
  12. # conntrack 上限与超时收敛(若启用 iptables/conntrack)
  13. net.netfilter.nf_conntrack_max = 262144
  14. net.netfilter.nf_conntrack_tcp_timeout_established = 1200
  15. EOF
  16. sysctl --system && sysctl -a | grep -E 'somaxconn|syn_backlog|port_range'
复制代码

别忘了应用侧这一半——这是最容易漏的:MySQL 的 back_log 参数(即 listen backlog)默认只有 80 左右,内核队列再大,实际生效的是 min(backlog, somaxconn):
  1. SHOW VARIABLES LIKE 'back_log';
  2. -- 修改 my.cnf:back_log = 2048,重启实例生效
复制代码

步骤 5:压测复现验证
  1. # 用并发建连工具模拟连接风暴(示例:mysqlslap 纯建连)
  2. mysqlslap --host=<db_ip> --user=test -p --concurrency=500 --iterations=3 --auto-generate-sql
  3. # 同时在服务端观察 Recv-Q 峰值与丢弃计数是否再增长
  4. watch -n 1 'ss -lnt | grep 3306; netstat -s | grep -i overflow'
复制代码

验收标准:压测期间 Recv-Q 峰值低于 Send-Q,overflow/discarded 计数停止增长,客户端建连耗时平稳。

四、实操检查清单


  • [ ] ss -lnt 确认监听端口的 Recv-Q 长期远低于 Send-Q;
  • [ ] netstat -s 的 listen queue overflows / request sockets discarded 两个计数纳入监控,出现环比增长即告警;
  • [ ] somaxconn 与 tcp_max_syn_backlog 已调整,且 sysctl -a 验证生效(不是只写了文件);
  • [ ] MySQL back_log、PostgreSQL listen_addresses 对应的 backlog 已与内核值对齐;
  • [ ] conntrack 机器:nf_conntrack_count/max 占比告警线设在 80%;
  • [ ] 短连接客户端:临时端口范围放宽 + 连接池化改造,客户端监控 TIME_WAIT 总量;
  • [ ] tcp_abort_on_overflow 仅在诊断期临时开启,用完关闭;
  • [ ] 任何内核参数变更前记录基线,变更后压测回归。


几个容易踩的坑


  • 队列大小取小不取大:内核 somaxconn 调到 4096,应用 backlog 还是 80,实际生效的是 80。两边必须同时调,MySQL 是 back_log,其他中间件各有对应参数。
  • TIME_WAIT 数量大不等于有病:单机几万个 TIME_WAIT 是主动关闭的正常代价。先确认是哪一端主动关闭、临时端口是否够用,再决定动不动 tcp_tw_reuse;tcp_tw_recycle 在 4.12 内核后已删除,NAT 环境下是历史事故之源,不要再写进文档里传播。
  • syncookie 不是万能药:tcp_syncookies=1 能在半连接洪水下保护服务端,但 syncookie 本身不带状态、会牺牲部分 TCP 选项,数据库主机靠"调大半连接队列 + 应对 accept 慢"才是正解。
  • 把连接超时当数据库慢来治:加 max_connections、重启实例都是无效动作。先分清"建连失败"和"已建连查询慢",两类问题根因链完全不同。
  • 容器/K8s 环境多一层:Pod 里的 somaxconn 是宿主机与 netns 两层都要看,istio/envoy sidecar 的 accept 线程也可能成为新瓶颈,排查时别只看数据库容器。


治理前后对比

指标治理前治理后
建连超时告警每周 3~5 次0 次(连续 60 天)
accept 队列溢出计数事故时段 10 秒 +1200长期 0 增长
建连耗时 P995.8 秒12ms
listen backlog(实际生效)802048
conntrack 表占用峰值97%31%
排查一次同类故障耗时半天~一天15 分钟内定位


一句话收尾:新建连接超时、存量查询正常,先怀疑内核队列与 conntrack,再看数据库。把 ss -lnt、netstat -s 的两个溢出计数放进日常监控,这类故障在业务报障之前就能被你先看见。
免责申明1、欢迎访问本站,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@dbabbs.com
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

QQ|Archiver|小黑屋|DBA论坛中国 ( 鲁ICP备20017503号-2 )

GMT+8, 2026-10-18 06:16 , Processed in 0.017354 second(s), 9 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表