x86服务器跑CDN要限制带宽,核心思路是在数据链路层用tc的htb队列做出口限速,再配合内核参数调优和Nginx层限速,三者叠加才能压得住。多数情况下,单靠应用层限速是不够的,因为CDN节点流量大、连接数多,内核协议栈本身也会成为瓶颈。
cdn节点服务器带宽限制设置方法:先搞懂流量从哪来
CDN节点的带宽消耗主要来自三部分:用户请求下行流量、回源流量、节点间同步流量,做限制之前,先用iftop或nload观察峰值时段,确认是哪个方向把带宽打满。
常见场景是用户侧下行流量占大头,比如视频分发节点,凌晨两点还有大量拖拽请求,这时候需要区分的是:是正常业务峰值,还是被刷流量,两种情况处理逻辑完全不一样。
用tc的htb队列做基础限速
tc(Traffic Control)是Linux内核自带的流量控制工具,htb(Hierarchical Token Bucket)是其中最适合做带宽限制的队列规则,操作路径如下:
# 清除现有规则 tc qdisc del dev eth0 root 2>/dev/null # 创建htb根队列 tc qdisc add dev eth0 root handle 1: htb default 10 # 配置总带宽限制,比如300Mbps tc class add dev eth0 parent 1: classid 1:1 htb rate 300mbit # 配一个子类,保证普通业务最低拿到200Mbps tc class add dev eth0 parent 1:1 classid 1:10 htb rate 200mbit ceil 300mbit
这里的rate是保证带宽,ceil是最大可用带宽,跑CDN的服务器通常是多核CPU,网卡队列多,建议按CPU核心数划分多个htb子类,避免单队列竞争导致吞吐量上不去。
iptables的connlimit别忽略
带宽限制不只是吞吐量的事,新建连接速率更关键,CDN节点最怕的是大量短连接打过来,每次都要过一遍协议栈,CPU很快就满了,用iptables的connlimit模块做连接速率限制:
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 2000 --connlimit-mask 32 -j DROP iptables -A INPUT -p tcp --dport 80 -m recent --name httpflood --update --seconds 60 --hitcount 3000 -j DROP
第一条限制单IP最大并发2000连接,第二条检测60秒内新建连接超过3000次的IP直接丢弃,这个配合tc一起用,效果比单独限带宽更理想。
x86架构cdn带宽控制方案对比:软件限速各有优劣
行业内常见的方案有tc限速、Nginx层限速、以及专门的流量控制工具,三者的适用场景不同。
| 方案 | 粒度 | 对CPU的影响 | 适用场景 |
|---|---|---|---|
| tc + htb | 设备级 | 低,内核处理 | 物理机整体出口限速 |
| Nginx limit_rate | 请求级 | 中等 | 对特定URL/客户端限速 |
| Trickle等用户态工具 | 进程级 | 较高 | 测试环境,生产不推荐 |
行业共识认为,生产环境优先用tc做第一层粗粒度限制,然后Nginx层做细粒度控制,这类组合方案能较好平衡性能和灵活性。
Nginx层的限速配置实操
Nginx有两个方向可以做限速:limit_rate控制单连接下载速度,limit_req控制请求速率。
location /videos/ {
limit_rate 500k; # 单连接限速500KB/s
limit_rate_after 5m; # 前5MB不限速,之后开始限
limit_conn perip 4; # 单IP最多4个并发连接
}
这里特别说明limit_rate_after的用法:前几MB不限速,是为了兼顾首屏体验,CDN的客户最在意首包时间,如果一上来就限速,TTFB会很难看,等流量跑起来再限制,体感会好很多。
XDP/eBPF方案是x86平台的加分项
近几年内核社区把XDP(eXpress Data Path)推进到了生产可用水平,它绕过了传统内核网络栈,直接在网卡驱动层做丢包或转发,对CPU占用极低。
用bpf_helpers写一个简单的限速程序,思路是维护每个IP的令牌桶,这个方案适合流量规模比较大的CDN节点比如单机跑满万兆网卡的情况,不过需要网卡驱动支持XDP,老款Intel 82599系列就不行,Mellanox CX4以上和部分新款Intel X710可以。
cdn服务器带宽跑满怎么解决:从内核参数到底层工具排查
带宽显示跑满,不一定是真的出口带宽不够,有可能是在某个中间环节卡住了,以下是排查顺序:
- 看有没有软中断瓶颈:
cat /proc/net/softnet_stat,第二列drop值持续增长说明CPU处理不过来,这时要调netdev_budget和net.core.netdev_max_backlog - 检查网卡多队列:
lspci -vvv确认网卡开启RSS,或者用ethtool -l eth0查看队列数 - 确认tc是否有丢包:
tc -s qdisc show dev eth0看dropped字段
内核参数调优清单
CDN节点是典型的网络密集型场景,以下参数直接影响带宽上限:
# 文件描述符上限要提高 echo "fs.file-max = 6553600" >> /etc/sysctl.conf # 连接队列加大 echo "net.core.somaxconn = 65535" >> /etc/sysctl.conf echo "net.ipv4.tcp_max_syn_backlog = 65535" >> /etc/sysctl.conf # 本地端口范围放宽,避免TIME_WAIT占满端口 echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf # TIME_WAIT快速回收 echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf sysctl -p
注意tcp_tw_reuse只对主动连接方有效,CDN节点多数情况是服务端,这个参数的作用有限,真正有效的是开启tcp_tw_recycle,但NAT环境下容易出问题,需谨慎,现在内核版本也倾向于废弃它。
PPS攻击导致带宽异常的处理
有一种情况是带宽没到上限,但网卡的小包处理能力先到顶了,比如每秒200万PPS的SYN Flood,每个包只有几十字节,带宽可能才跑200Mbps,但CPU已经100%了。
这种场景需要配置网卡的RSS队列数,以匹配CPU核心数量,同时可以给iptables加一条针对小包的限制规则:
iptables -A INPUT -p tcp --syn -m length --length 40:60 -m limit --limit 10000/s --limit-burst 20000 -j ACCEPT
Linux物理机和虚拟化场景的带宽限制差异
跑CDN的x86服务器,有的是物理机安装,有的则基于KVM虚拟化环境,两种场景在限速方式上有所不同。
物理机直接操作eth0即可(使用上述tc规则),而KVM环境下,需要关注宿主机和虚拟机两层:
- 宿主机上通过
tc限速vnet0(虚拟网卡对应的tap设备), - 虚拟机内部对
eth0限速。
需要注意,宿主机的tc优先级更高,虚拟机内部的限速规则不会超越它,具体操作是在宿主机执行:
tc qdisc add dev vnet0 root handle 1: htb tc class add dev vnet0 parent 1: classid 1:1 htb rate 100mbit
这种方案比较适合中小规模cdn节点省钱方案一台物理机跑多个虚拟机实例,每个客户分配独立的带宽上限。
调度层配合:不只靠单机限速
单机限速只是第一步,比较正规的CDN架构会把调度层和节点层分开,DNS调度或HTTPDNS会根据节点的实时带宽水位做流量分配。
实际部署时可以在节点上定期把带宽数据上报给调度中心,当节点带宽快满时,调度中心就少分配新流量过来,这种联动机制需要运维同学自己设计,但多数情况下可以先用简单的脚本实现。
比如写一个cron任务,每30秒检测一次实时带宽:
#!/bin/bash bandwidth=$(cat /sys/class/net/eth0/statistics/rx_bytes) sleep 30 bandwidth_later=$(cat /sys/class/net/eth0/statistics/rx_bytes) rate=$(( (bandwidth_later - bandwidth) 8 / 30 )) echo "$rate" > /var/run/node_bw_usage
调度层写个接口读这个数值,超过阈值就把节点标记为overloaded,这个方案不复杂,但很实用。
Q&A:常见带宽控制问题
Nginx层限速和tc限速冲突吗?
不冲突,tc是在内核协议栈的出口处生效,Nginx的limit_rate是在应用层控制每个连接的发送速率,两者作用点不同,可以同时使用,tc粒度更粗,保证整个网卡出口不超限;Nginx粒度更细,精确控制每一类请求的速度。实际部署时建议都用,tc做兜底,Nginx做精细化控制。
其他软件如varnish、atlas的限速配置在哪里?
varnish没有内置的限速模块,需要配合vmod或者前置的负载均衡器做,atlas(Apache Traffic Server)的限速在records.config里配置,核心参数是proxy.config.http.insert_request_via_str和proxy.config.http.limit_rate,单位是字节/秒,但实话说,varnish和nginx在动态请求限速方面没有本质区别,核心都是靠上游应用配合。
x86平台上iptables对带宽限制有用吗?
iptables本身不限带宽,但可以用hashlimit模块做速率匹配,也算是间接限制:
iptables -A FORWARD -p tcp --dport 80 -m hashlimit --hashlimit-above 1000/s --hashlimit-burst 2000 --hashlimit-mode srcip -j DROP
超过每秒1000次连接请求的IP会被丢弃,这个方案适合防CC攻击场景,不过对吞吐量限制没用,吞吐量限制还得靠tc。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/771658.html


