CDN TCP看似简单,真正落地时却很容易踩坑。打开网站,页面转圈半天,最后浏览器弹出“504 Gateway Time-out”——这大概是运维新手最怕看到的报错之一。很多人第一反应是去看源站的应用日志,但翻了一遍发现Nginx、PHP-FPM都正常,内存、CPU也还有富余,那问题到底出在哪?
如果你使用了CDN(Content Delivery Network,内容分发网络),504通常意味着“CDN节点向源站发起TCP连接时,源站在规定时间内没有完成握手”。换句话说,问题很可能不在应用层,而在更底层的网络堆栈里——最常见的是源站的内核TCP半连接队列被填满,或者防火墙因为连接频率过高直接丢掉了SYN包。
先搞清楚基本的TCP握手与队列
要理解半连接队列,先要回忆一下TCP三次握手的过程。客户端(CDN节点)发送SYN包,源站内核接收到SYN后,会把这个连接标记为“半连接”状态,放入一个叫做半连接队列(SYN Queue)的数据结构里,然后回复SYN+ACK。客户端再发回ACK,连接进入“全连接”状态,应用程序(比如Nginx)才能从全连接队列(Accept Queue)里取出这个连接来处理。
半连接队列的大小由内核参数 net.ipv4.tcp_max_syn_backlog 控制,系统默认值通常只有128或256。当CDN在短时间内(比如秒杀活动或流量突增)向源站发起大量并发连接时,半连接队列会迅速占满。新来的SYN包无法入队,内核只能丢弃——于是CDN节点一直等不到SYN+ACK,最终触发超时,返回504。
全连接队列也同样重要
如果半连接队列没事,但全连接队列满了呢?同样会丢连接。全连接队列大小由应用程序的 backlog 参数(如Nginx的 listen backlog)和内核参数 net.core.somaxconn 共同决定。排查时这两个队列都需要检查。但根据我的经验,CDN回源场景下,半连接队列溢出的概率远高于全连接队列,因为CDN节点的分布特性会让源站同时收到大量来自不同IP的SYN请求。
补充参考:此处可内链到“CDN TCP故障排查实例”。
相关阅读:此处可内链到“CDN TCP常见问题”专题。
防火墙限频:另一个常见的“隐形杀手”
除了内核队列,还有一个容易被忽视的因素:源站上的防火墙规则。很多系统管理员为了防CC攻击,会在iptables里通过 limit 或 hashlimit 模块限制单个IP的连接速率。但如果这个限速规则写得太死——比如 -m limit --limit 5/s——当CDN的多个节点(虽然IP不同)同时请求时,防火墙会按源IP独立计数,正常情况不会触发。但若规则错误地使用了 --limit-burst 或没有正确匹配连接状态,就可能把CDN的合法SYN包当成“超过限速”直接DROP。更糟糕的是,一些防火墙默认配置会丢弃超过 nf_conntrack 表容量的新连接,导致SYN包石沉大海。
如何区分问题出在内核队列还是防火墙?
排查方法很简单:登录源站服务器,执行以下命令:
ss -s
查看输出中的 TCP: listen overflows 和 TCP: listen drops 值。如果这些数值在不断增长,说明半连接队列或全连接队列发生了丢包。随后用 sar -n TCP,ETCP 1 实时观察 listen overflows 变化。
如果队列没有溢出,丢包依然发生,那就要检查防火墙。执行 iptables -L -n -v 查看每个规则的 pkts 计数,特别是INPUT链中针对SYN包的DROP规则。如果计数迅速增加,说明防火墙在“背锅”。
延伸阅读:此处可内链到“CDN TCP配置案例”相关文章。
调优实战:解决半连接队列溢出的标准操作与CDN TCP
我的处理经验
确认问题后,按以下步骤操作:
- 增大半连接队列:编辑
/etc/sysctl.conf,添加或修改:net.ipv4.tcp_max_syn_backlog = 8192net.core.somaxconn = 2048
注意tcp_max_syn_backlog不能超过net.core.somaxconn,建议后者调整为前者一半或相等。 - 启用SYN Cookie:这是一种防御SYN Flood的机制,当半连接队列满时,内核会用Cookie代替队列来保存连接信息。虽然会略微增加CPU开销,但对现代服务器影响很小。添加:
net.ipv4.tcp_syncookies = 1 - 调整Nginx的backlog:在server块中设置
listen 80 backlog=1024;或listen 443 ssl backlog=1024;,使应用程序能够接收更多等待中的连接。 - 优化防火墙规则:如果使用了
connlimit或limit模块,检查是否误限。对于CDN回源,建议在防火墙规则中放行CDN节点IP段(可以从CDN控制台获取),或者将限速阈值调高到100/s以上。
验证与监控:确保改动生效——CDN TCP
先看关键判断
执行 sysctl -p 使内核参数生效。重启Nginx。然后用压测工具(如wrk)模拟大量并发连接,观察 ss -s 中的 listen overflows 是否归零。同时用 tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' 抓包,确认源站回复了SYN+ACK,没有丢包。
想继续深入:此处可内链到“CDN TCP优化清单”文章。
小结:别只盯着应用日志
先看关键判断
下次遇到CDN回源504,先别急着重启Nginx或找CDN客服。登录源站,先看内核的TCP半连接队列有没有溢出,再看防火墙有没有丢SYN。这两个问题在无状态的TCP层,应用日志通常没有任何记录。理解它们的工作原理,你就能在几分钟内定位根因,而不是靠运气盲猜。
当然,流量突增是诱因。如果调优后发现高并发场景下依然频繁超时,那就要考虑扩容源站带宽、增加CDN缓存命中率、甚至升级到多节点负载均衡了。但这是后话——先解决眼前的504再说。真正做好CDN TCP,靠的不是参数堆砌,而是持续验证。
延伸阅读
