资讯详情

资讯详情

CentOS上使用tc进行网络带宽限流实战指南

1. 讲在最前为什么要在 CentOS 上做限流先说个真实场景。前阵子给一台 CentOS 7.9 的服务器做维护这台机器跑着几个 Java 服务和一台 Nginx 反代。下午高峰期 CPU 不高、内存也够但用户就是反馈“卡成幻灯片”。登上去一看网卡流量直接打满了 300Mbps三个服务都在抢带宽数据库备份和日志同步又在里面凑热闹。你很难说是哪个进程的问题因为谁都在跑谁都缺带宽。这时候最直接的办法就是限流——不是杀进程也不是拔网线而是让每个服务各拿各的带宽互不踩踏。有人会问限流不是应该做在应用层吗比如 Nginx 的 limit_req、Sentinel 的 QPS 限流、AOP 注解限流。这些确实能挡住集中在某一个接口上的突发请求但它们管不住流量本身。如果一台 CentOS 服务器上有多个业务共用一个网卡或者某个服务起了一堆连接疯狂下载应用层限流完全看不到这些。真正的兜底方案是在 Linux 内核的网络协议栈里下手用 tcTraffic Control流量控制对网络接口做带宽整形。它不管你是 HTTP、MySQL 还是 rsync只要是走了这个网卡的包就得按你设定的规则排队。tc 是 Linux 自带的流量控制工具在 CentOS 上开箱即用不需要额外装什么重的依赖。它最大的价值是可以在不重启服务、不打断连接的情况下动态修改某一类流量的带宽上限。想限下行带宽就限下行想限上行就限上行想按 IP 限就按 IP 限想按端口限就按端口限。这种灵活性是 iptables 的 limit 模块做不到的——iptables 那个 limit 是限制报文速率用的主要防 SYN Flood和 tc 的带宽整形完全两码事。这篇我把自己在 CentOS 7 和 CentOS Stream 9 上实际用 tc 做限流的经验完整整理一遍覆盖了原理、命令、脚本、常见坑以及一些网上的文档里很少写清楚的细节。不管你是要限制某个容器跑到外面的流量还是想给家宽做一套按 IP 分配的限速规则这套思路都通用。2. tc 限流的原理搞清楚 HTB 和令牌桶再动手2.1 一句话理解流量控制Linux 内核把网卡收发的数据包交给一套队列规则qdiscQueueing Discipline来管理。默认情况下网卡的 qdisc 是 pfifo_fast就是先到先走大家挤在一起谁也别想有保障。tc 干的事情就是替换掉这个默认 qdisc换成我们自定义的规则比如 HTBHierarchy Token Bucket层级令牌桶。数据包到了以后先按类别分类再按各类的速率限制放进队列最后由内核按优先级和速率发包。tc 的配置对象主要有三个qdisc队列规则负责决定包怎么排队、怎么发送。常见的有 pfifo_fast、htb、tbf、netem。class类别挂在 qdisc 下面每个类别可以有自己的带宽上限和优先级。filter过滤器决定哪些包进入哪个类别。filter 的匹配依据可以是 IP、端口、协议、mark 标记等。这三者的关系可以这样理解qdisc 是一个大水管的总阀门class 是下面分出来的若干根小水管filter 是分拣员把不同来源的水引入对应的小水管。2.2 HTB 和 tbf 怎么选都试过之后我的结论HTB 是现在做多级限速的主流方案。它支持在一个根 qdisc 下挂多个 class每个 class 可以独立设置 rate保证带宽和 ceil最大带宽。如果 A 类闲着不用带宽B 类可以临时借用这就是 HTB 最有价值的地方——带宽限制只在拥挤的时候生效不限制闲置时的突发能力。TBFToken Bucket Filter令牌桶过滤器则简单得多就是给一个网卡设置一个固定的速率上限没有子类没有优先级。适合那种“不管谁来整体不能超过 xx Mbps”的场景。比如服务器只有一条 10Mbps 出口想给它全局限个速直接用 tbf 就够了。如果要做精细化限速比如一个 IP 一个带宽、或者按端口区分服务优先级必须用 HTB。这是我踩过几次坑后的结论。TBF 看着简单但一旦需要调整某个子服务的速度就得把整个命令翻出来改一遍而 HTB 只需要动某一个 class 的参数。注意tc 限速对 eth0 这种物理网卡是限制出方向egress的。所谓“下行”限速实际上要在入口网卡上做一个 ifbIntermediate Functional Block来实现。多数教程只讲 egress这个细节后面单独说。2.3 令牌桶的关键参数rate、ceil、burst用 HTB 时最核心的就是 rate、ceil 和 burst 这几个词理解它们的区别能让限流结果更可控。rate每个 class 保证的带宽。就算网络再忙这个速度也是留给它的。ceil这个 class 能借到的最大带宽。当别的 class 有空闲带宽时可以突破 rate 冲上去但超过 rate 的部分是不保证稳定的。burst允许突发的字节数。TCP 启动时有个慢热过程如果没有 burst刚开始的几个包就会因为超出速率被丢掉。burst 的设置对 SSH、HTTP 这类交互式请求影响很大。再补一个后面实际配置会遇到的数字在 HTB 中计算 burst 时一般建议取 rate 的千分之一到百分之一之间。比如 rate 是 1Mbpsburst 可以设成 10Kb 到 100Kb太小会频繁丢包太大又起不到突发抑制的作用。这里有个隐藏坑tc 的速率单位是 bit小写 b而文件中看到的大写 B 是 Byte。1Mbps 1000kbit而不是 1MB/s。很多人在换算时弄混了 rate1000kbit 和 rate1000KByte结果速度完全不达标查了半天网络问题最后发现是单位搞错了。3. 实际配置方案从零开始的 tc 限流命令3.1 安装依赖和基本检查CentOS 上 tc 命令来自 iproute2一般最小化安装就有。如果没有先装一下yum install -y iproute2然后确认网卡名称。CentOS 7 通常是 eth0CentOS Stream 8/9 可能是 ens160 或 ens192云服务器上也可能叫 eth0。用 ip addr 看一下实际名字ip addr show我这边实验机器是 ens160。下面的命令都以 ens160 为例。注意tc 命令作用于网卡时如果网卡已经被 NetworkManager 管理修改后不会自动持久化重启或重载网络服务后规则会丢。后面讲持久化方案时会提到。3.2 先做一个简单的全局限速限速最入门的需求是“这台机器出口带宽最高 50Mbps”。直接挂一个 tbf 就行tc qdisc add dev ens160 root tbf rate 50mbit burst 10kb latency 50ms这条命令给 ens160 的根队列换成了 tbf速率 50Mbps突发 10KB。命令执行后不会有任何输出没有消息就是好消息。想看配置结果tc qdisc show dev ens160看到类似这样的输出说明生效了qdisc tbf 8001: root refcnt 2 rate 50Mbit burst 10Kb lat 50.0ms清掉这条规则也很关键因为改错配置的时候你要能恢复默认状态tc qdisc del dev ens160 root3.3 用 HTB 做多类别限速限制某个 IP 的带宽单机限流用场景有限实际碰得最多的还是“限制某个内网 IP 不要占满全部带宽”。比如这台 CentOS 上跑了多个服务有一个下载服务是 192.168.1.100它经常跑出异常流量拖死整个网卡现在要把它限到 10Mbps。第一步给 ens160 创建一个 HTB 根节点总速率假设是 1000Mbps也就是理论上的网卡速率或者你的实际出口带宽tc qdisc add dev ens160 root handle 1: htb default 30这里 handle 1: 是给根队列一个编号default 30 表示没有匹配到任何 filter 的流量会走 class 1:30。这个一定要设好不然所有流量会被丢掉服务器直接断网。第二步创建默认类和高优类tc class add dev ens160 parent 1: classid 1:1 htb rate 1000mbit ceil 1000mbit tc class add dev ens160 parent 1:1 classid 1:30 htb rate 900mbit ceil 1000mbit tc class add dev ens160 parent 1:1 classid 1:10 htb rate 10mbit ceil 10mbitclass 1:1 是所有子类的父节点class 1:30 是默认流量class 1:10 是我们要限速的受限流量。第三步把 192.168.1.100 的包分拣到 class 1:10tc filter add dev ens160 parent 1: protocol ip prio 1 u32 match ip dst 192.168.1.100 flowid 1:10注意这里用的是 dst因为要从这台服务器角度限制它发送给这个 IP 的数据。反过来要限制它上传服务器从这个 IP 收数据需要改 src 并用 ifb这个后面铺开讲。到这里就能测一下了。在这个 CentOS 上往 192.168.1.100 打流量测速结果应该稳定在 10Mbps 上下动态变化的流量不会超过 10Mbps。如果发现别的服务也被拖慢了检查一下 filter 的优先级是否正确。例如若还有服务从同一个 IP 访问但是希望不受限制可以给默认类也加一条精确匹配tc filter add dev ens160 parent 1: protocol ip prio 2 u32 match ip dst 192.168.1.200 flowid 1:30但其实默认类已经处理了所有未匹配的流量除非你有更复杂的匹配需求否则不需要单独加。3.4 限制多个 IP 的最快方式脚本批量生成到了生产环境不可能手动一条条敲 filter。我写了个简单的 shell 命令来批量限速这里把思路放出来。假设有个 ip 列表文件 /opt/limit_ip.txt每行一个 IP192.168.1.100 192.168.1.101 192.168.1.102批量创建 class 和 filteri10 for ip in $(cat /opt/limit_ip.txt); do tc class add dev ens160 parent 1:1 classid 1:$i htb rate 10mbit ceil 20mbit tc filter add dev ens160 parent 1: protocol ip prio 1 u32 match ip dst $ip flowid 1:$i i$((i1)) done这是一个很实用的基准方案再往上层加业务逻辑可以写成一个配置文件驱动。个人经验是classid 不要用连续的大数字混在一堆建议定义一个对应关系表比如 1:10 对应 10Mbps、1:11 对应 20Mbps方便后面维护。3.5 限制端口与协议给 MySQL 或备份通道限速有些服务占用带宽不是按 IP 来的而是按端口。比如这台 CentOS 上有 MySQL 在同步 binlog或者有一个备份程序向异地仓库推送数据它可能不按 IP 走或者目标 IP 不能动这时候就按端口过滤。限制 ssh 流量最高 1Mbps还是用前面的 HTB 结构tc filter add dev ens160 parent 1: protocol ip prio 1 u32 match ip dport 22 0xffff flowid 1:20这里补充一点u32 匹配端口时dport 和 sport 的写法是不同的。限制发往 3306 的数据要在匹配 dst IP 后再加一个 dport 匹配。一个更稳的方案是先用 iptables 打 mark然后用 tc filter 根据 mark 分类。这样的好处是 iptables 的匹配能力远比 u32 强支持 conntrack 状态、服务名等同时 filter 规则不用频繁改。iptables -t mangle -A OUTPUT -p tcp --dport 3306 -j MARK --set-mark 2 tc filter add dev ens160 parent 1: protocol ip prio 1 handle 2 fw flowid 1:20第二行的 handle 2 fw 表示按防火墙标记为 2 的包来分类flowid 1:20 是预设好的慢速类。特别提醒iptables 的 MARK 要在 OUTPUT 链上打而不是 FORWARD 链。tc filter 工作在 IP 协议栈的队列处它看的是已经完成的 skb mark。如果打在 FORWARD 链上包的 mark 可能没被 tc 看到限流会失效这个问题当时排查了很久。3.6 实现下行限速ifb 网卡的作用前面所有配置都是限制 egress。现实中更常见的是限制“服务器下载速度”。比如 CentOS 上跑了一个监控系统需要限制从源站拉镜像的下载带宽不能把整个机房出口撑满。默认的 tc 对 eth0 的入口收包只能丢不能管速率所以要用 ifb 把入口流量重定向到一个虚拟设备上再限。第一步加载模块并创建 ifbmodprobe ifb numifbs1 ip link set dev ifb0 up第二步把 eth0 的入口流量重定向到 ifb0tc qdisc add dev ens160 handle ffff: ingress tc filter add dev ens160 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0第三步在 ifb0 上做和上面一模一样的 HTB 配置但方向的含义反转了。此时在 ifb0 上 rate 10mbit 限制的就是 ens160 实际收包的速度tc qdisc add dev ifb0 root handle 1: htb default 30 tc class add dev ifb0 parent 1: classid 1:1 htb rate 1000mbit tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 10mbit ceil 10mbit tc filter add dev ifb0 parent 1: protocol ip prio 1 u32 match ip src 192.168.1.100 flowid 1:10ifb 是一个坑点比较多的工具最大的问题是加载模块后 ifb0 不一定自动 up忘了 up 的话包会进去之后直接被丢掉然后服务器入口速度直接归零。其次是如果虚拟化平台里网卡本身不支持 offload 特性mirred 重定向可能不稳定。4. 让限流规则动起来动态调整与实时监控4.1 动态修改带宽不需要删掉重建有同学可能觉得要改变一个 class 的带宽是不是要把整个规则链删掉再重来一遍。完全不需要。tc 提供了 change 命令可以原地调整参数而不用中断现有连接。tc class change dev ens160 classid 1:10 htb rate 5mbit ceil 10mbit这条命令把 1:10 的保证速率从 10Mbps 降到 5Mbps瞬间生效。这个特性非常实用。比如你白天给某个服务 30Mbps晚上高峰期需要压到 5Mbps写一个 crontab 脚本在特定时间点 change 一下就行不用像以前那样每次敲一堆删除再重建的命令。不过要记得一个细节change 命令只改 class 的速率参数不会动 filter。也就是说 filter 还是保留的绑定关系不会断。这在我实测中很舒服。4.2 查看当前规则的几条命令限流配好之后总得知道它到底有没有生效。通则不痛。查看所有 qdisctc qdisc show能确认根规则是 htb 还是 tbf。查看所有 classtc class show dev ens160能看到每个 class 的 rate 和 ceil以及实时的 bytes 和 pkt 计数。查看所有 filtertc filter show dev ens160确认流量分类规则有没有挂上。经验之谈判断限流是否在跑的最快方式不是看规则而是看 class 的 bytes 是否在增长。如果 class 1:10 的 bytes 一两分钟不见涨说明根本没有包走进这个类里filter 可能写错了而不是规则没生效。watch -n 1 tc -s class show dev ens160-s 选项特别重要它会显示每个 class 的收发统计、dropped、overlimits 这些计数器。排查丢包时这些数字比什么工具都直接。4.3 删除和清理规则的正确姿势完整清理一个网卡上的所有 tc 规则tc qdisc del dev ens160 root如果之前配了 ifb 和 ingresstc qdisc del dev ens160 ingress tc qdisc del dev ifb0 root我遇到过一种情况删了根规则后流量控制恢复正常了但发现 iptables 的 mark 还在结果后续别的程序又被 mark 干扰。所以清理 tc 规则时也要回头检查一下有没有连带打 mark 的 iptables 规则需要一起清理。开发调试时我习惯先执行tc qdisc del dev ens160 root 2/dev/null把所有规则清干净再把新规则加进去。这样也能避免重复添加 root qdisc 导致的 RTNETLINK answers: File exists 报错。5. 实战踩坑录这几个问题能让你排队到半夜5.1 限速完全无效现象tc 配置看起来都对但实际测速没变化。排查步骤确认 tc 规则在哪个网卡。如果有 bond 网卡eth0 上的规则不生效是正常的要加在 bond0 上。确认网卡有没有被 NetworkManager 重建。有些版本的 CentOS 在网卡 link 状态变化时会触发重载配置把 tc 规则冲掉。确认流量是不是走了别的路径。比如服务绑定在 lo 上或者有多个路由出口这时候你在 eth0 上做限制毫无意义。我自己最常犯的错是服务器上有两个网卡 eth0 和 eth1物理线路从 eth0 出但程序监听在 eth1。流量根本没走 eth0自然限了个寂寞。查看当前路由确认流量实际走哪个接口ip route5.2 设置后服务器断网这是最紧张的故障。执行完 tc qdisc add 后 SSH 立即卡死很多人的第一反应是赶紧重启其实还有救。断网的原因通常是 default 类没设对或者 filter 把 SSH 的包也丢进了一个不存在或超低速的类。补充解法在执行任何 tc 操作前先写一条定时清空命令保命。(sleep 60; tc qdisc del dev ens160 root) 这样就算配置失误导致断网一分钟内也会自动恢复。等确认规则没问题后这条命令自然失效。有人说这是“炸了也能救回来”的兜底方案我强烈建议生产环境用这条。5.3 burst 设置引起的 SSH 卡顿有次给一台机器限 2Mbpsburst 设了 1kb。结果 SSH 操作明显一顿一顿的敲个命令都要等一两秒才回显。查了半天发现是 burst 太小导致 SSH 的 TCP 握手和窗口更新包都被丢了。TCP 在交互式连接中会有很多小包这类包如果 burst 给得太小很容易被误杀。解决方法是给 SSH 单独开一个 class或者把 burst 调大到 rate 的 10% 左右。对于交互式远程管理我建议给 SSH 单独建一个高优先级 class。比如所有流量默认 10MbpsSSH 单独 1Mbps 不参与共享tc filter add dev ens160 parent 1: protocol ip prio 0 u32 match ip dport 22 0xffff flowid 1:9prio 0 优先级最高先于其他规则匹配保证 SSH 始终可用。这样即使总带宽打满了远程管理也不会卡死。5.4 容器或虚拟机的流量限不住KVM 虚拟机或 Docker 容器里的流量经过宿主机的网桥如 virbr0 或 docker0直接在物理网卡 ens160 上配置 tc经常会发现对虚拟机内测速不起作用。原因是虚拟机流量由宿主机的 vnet/eth 对接到网桥再通过物理网卡出去。filter 在处理网桥流量时会走 br0 的逻辑而且容器网络用的是 veth pair包经过的路径是 veth - docker0 - ens160如果你只在 ens160 上做 u32 dst 匹配容器的包不带目标 IP或带的是容器内部 IP可能匹配不上。这个时候有两类解决办法在宿主机的网桥上做限速对虚拟机的 vnet 接口直接挂 tc比如限 vnet0 的速率不碰物理网卡。在 docker0 网桥上做整网段限速直接匹配容器网段比如 172.17.0.0/16。这里有个实践结论对虚拟机的 vnet 接口做限制比对物理网卡做过滤要好控制得多毕竟隔离性好不会影响宿主机上其他服务。5.5 限速规则重启后消失这是 tc 和 iptables 的一大区别tc 规则不持久化。服务器重启之后所有 qdisc、class、filter 全部清空。解决办法是写一个开机自启脚本systemd 服务或 rc.local 都行。我习惯写一个脚本 /usr/local/bin/tc-limit.sh把前面所有命令按顺序整理好然后用 systemd service 拉起。chmod x /usr/local/bin/tc-limit.sh echo /usr/local/bin/tc-limit.sh /etc/rc.local chmod x /etc/rc.localrc.local 比较直接但不推荐在 Systemd 系统上依赖它因为 rc-local.service 可能没启用。更稳的是写一个 unit[Unit] DescriptionTC bandwidth limit rules Afternetwork-online.target Wantsnetwork-online.target [Service] Typeoneshot ExecStart/usr/local/bin/tc-limit.sh [Install] WantedBymulti-user.target保存到 /etc/systemd/system/tc-limit.service然后systemctl daemon-reload systemctl enable tc-limit systemctl start tc-limit测试阶段可以先 start 看看脚本有没有报错。6. 进阶玩法tc 结合脚本做的几个实用场景6.1 按时间段自动切换带宽限流不是静态配一次就完事了。比如互联网出口带宽晚上八点到十一点是高峰期我想把内网视频服务临时压到低速其他时间恢复。写一个 crontab 就行# 每天20:00把视频服务 class 1:50 压到2Mbps 0 20 * * * tc class change dev ens160 classid 1:50 htb rate 2mbit ceil 5mbit # 每天23:00恢复10Mbps 0 23 * * * tc class change dev ens160 classid 1:50 htb rate 10mbit ceil 20mbitcrontab 里写命令时建议用绝对路径因为 cron 环境变量很少直接写 tc 可能找不到命令。用which tc查一下路径一般是 /usr/sbin/tc。6.2 检测大流量 IP 并自动限速生产环境里经常有某些业务突然产生大量下载流量把其他服务拖垮。结合 iftop 或 nethogs 找到大流量 IP再用脚本自动把它塞进限速类。我写过一个粗略的巡检脚本逻辑用 iftop -t -s 5 采集一段时间的流量统计拿到 TOP IP。判断这个 IP 的流量是否超过阈值。如果超了调 tc filter 把这个 IP 导进慢速类。记录日志到文件方便回溯。注意这一步要判断 IP 是不是基础设施比如 DNS、网关、监控服务器否则容易误伤。LOG/var/log/tc-auto-limit.log THRESHOLD_MBPS80 TOP_IP$(iftop -i ens160 -t -s 5 -n -N | awk /^ [0-9]\.[0-9]\.[0-9]\.[0-9]/ {print $2, $7} | sort -k2 -rn | head -1 | awk {print $1}) # 这里简化了逻辑实际应计算带宽超出才限速 tc filter add dev ens160 parent 1: protocol ip prio 1 u32 match ip dst $TOP_IP flowid 1:80 echo $(date) limit $TOP_IP $LOG这种自动化的思路在小型集群里非常有用。真实环境里的流量模型往往是波动的静态限速只能限制峰值动态限速才能真正保护其他业务。6.3 tc 配合 ipset 做动态黑名单限流ipset 是个好搭档。一个常见场景是限制某些恶意 IP 的访问带宽直接丢弃显得太狠不加限制又怕拖垮服务。用 ipset 维护一个恶意 IP 集合然后用 tc filter 匹配这个集合。先把 IP 加入 ipsetipset create badip hash:ip ipset add badip 1.2.3.4然后给 tc 加一条匹配 ipset 的 filtertc filter add dev ens160 parent 1: protocol ip prio 1 ipset match set badip dst flowid 1:60这句代码我实际用下来是可以的但要注意 ipset 需要和 iptables 配合才能准确判断是哪个 IP 的流量。更重要的是ipset 默认超时时间很长如果恶意 IP 不再活跃要定时清理 ipset 条目否则会一直占用慢速类资源。6.4 与 Sentinel 等应用层限流的配合定位最近看到不少关于 Sentinel 限流和熔断降级的讨论。它在 Java 应用层做 QPS 控制很成熟但如果有人问“为什么配置了 Sentinel 限流带宽还是被占满”答案就落在网络层。Sentinel 限的是应用本身的 QPS它默认认为业务服务和外部流量之间是直连的。如果前面挂了 Nginx 反向代理或者有多个服务共享同一个网卡Sentinel 只能管到应用自己管不到操作系统发送队列里的其他包。我处理过的一个案例Nginx 上配了 Sentinel 的 QPS 限流规则接口并发控制得很好但静态资源被人用多线程下载器拉取带宽被刷满。最后解决办法是在 Nginx 服务器上对下载请求的流量做 tc 限速比如限制 50Mbps应用层 QPS 和网络层带宽双管齐下服务才稳住。所以在排查限流问题时要分清楚每一层都做了什么层面工具控制目标应用层Sentinel、Nginx limit_reqQPS、并发数传输层iptables limit包速率、连接数网络层tc HTB/TBF带宽占用、优先级三层不是替代关系是互补关系。如果带宽被打满但 QPS 不高说明攻击或占用在应用层之下直接回到 tc。如果 QPS 很高但带宽没满说明请求很轻但量大限连接数或 QPS 会更有用。6.5 用 tc 给 KVM 虚机单独限速用 KVM 虚拟化时每个虚机的虚拟网卡在宿主机上对应一个 vnetX 接口。想给某个虚机限速只需要在这个 vnetX 上配 tc而不需要动物理网卡tc qdisc add dev vnet0 root handle 1: htb default 10 tc class add dev vnet0 parent 1: classid 1:1 htb rate 100mbit tc class add dev vnet0 parent 1:1 classid 1:10 htb rate 20mbit ceil 20mbit这样虚机无论如何跑出方向最多 20Mbps。注意这仍然是出方向如果还要限制虚机的下载方向就要在 vnet0 上挂 ifb。这套方案对 OpenStack 环境也适用只是要注意 OpenStack 的 agent 可能会时不时刷新网卡上的 qdisc需要持久化脚本每隔几分钟校验一下规则还在不在。7. 需要注意的几个底层细节7.1 tc 的速率单位换算这是新手最容易踩的坑。tc 中rate 10mbit 表示 10 兆比特每秒实际带宽是约 10Mbps。rate 10Mbit 同理大小写不敏感但要注意 M 是 1024 还是 1000。rate 10kbyte 或 10KB 表示字节速率和比特相差 8 倍。在上面的测试中如果 rate 设成 1024kbps 而实际上你希望是 1Mbps偏差其实很大。更稳妥的方法是统一用 mbit比如 5mbit、10mbit写起来简单算起来也直观。7.2 网卡对 tc 的影响有些网卡驱动和 tc 配合不好。特别是具备 TCP Segmentation OffloadTSO或 Generic Receive OffloadGRO的网卡tc 看到的可能是已经分段/合并的大包导致速率计算出现偏差。如果发现 tc 限流值极不准确考虑临时关闭网卡 offload 试试ethtool -K ens160 tso off gso off gro off关闭 offload 后对转发性能会有轻微影响但这种影响在我们内部环境中几乎不可感知。在调试问题期间关闭找到原因后再决定是否重新开启。7.3 虚拟化环境中的网卡名称不固定云服务器或虚拟机在重启后网卡名称可能从 ens160 变成 ens192。如果你把 tc 规则写死了网卡名重启后脚本就会报错。稳妥的做法是在脚本中动态获取第一个物理网卡名DEV$(ip route | grep default | awk {print $5} | head -n1)或者根据 MAC 地址绑定的 interface 文件来固定设备名。CentOS 7 中可以通过修改 /etc/sysconfig/network-scripts/ifcfg-* 来固定设备名但改动网卡名有系统风险需要一个一个排查依赖。个人建议是在启动脚本里加一层判断找不到网卡时直接报错退出而不是继续执行后面的命令这样才能尽早发现问题。7.4 tc 规则的叠加性tc 规则是增量式的。如果你在一个网卡上 add 两次根 qdisc 会报错但在同一个根 qdisc 下加多个 class 和 filter 是完全允许的。这也意味着你可以在运行过程中不断加新规则而不影响已有的限速。不过要注意filter 是按 prio 依次尝试的。如果两条 filter 的 prio 相同且都匹配同一个包内核会根据 filter 的添加顺序来决定先走后走所以设计 filter 时要给特殊流量安排更高的 prio数字更小默认类放最后。8. 末尾再补充一些心得这台 CentOS 7.9 服务器上我最终保留的限速方案不复杂一个 HTB 根节点下面分了三类——SSH 管理流量高优 10Mbps、数据库同步 20Mbps、其余默认 100Mbps再配一个定时清理脚本检查规则是否存在。整体跑下来即使文件同步服务开始疯狂拉数据SSH 也没有出现卡顿的情况。弄完 tc 限速后有几个连带的好处是意料之外的。比如有次某个 Java 服务出现了内存暴涨伴随着大量线程建立连接以前这种异常流量会直接打满出口带宽导致其他服务雪崩。有了 tc 的限制异常流量被压到了很低的速率故障被局限在单个服务范围内整个服务器没有出现大的抖动。这种“反而成了保护机制”的效果是我在配置之前真没想到的。最后分享一个排查技巧如果你不确定 tc 规则是否生效最简单的方法是看 /proc/net/psched这是一个内核提供的信息文件能看到当前的 qdisc 状态。配合 tcpdump 抓包判断基本上所有限流失效的问题都能定位到。如果在限流过程中碰到什么奇怪的坑记住一个原则先把 tc 规则删干净再一条一条加回去确认。大多数问题不是内核不支持而是之前的残留规则叠加在一起导致的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →