Linux 流量控制 (tc):实用指南
12 分钟阅读 - 2026年6月5日

使用 tc 在 Linux 上控制带宽、确定流量优先级以及塑造入口和出口。为实际服务器配置 HTB、IFB、DSCP 和 fq_codel。
tc 的工作原理
每个 tc 配置均由四个动态组件构成:
- qdisc(队列纪律)。这是连接到网络接口的调度器,负责决定数据包如何入队和出队。
- 类(Class)。这是基于类的 qdisc 内部的子划分。可以将其视为一条拥有自身限速的车道。
- Filter(过滤器)。检查数据包头(IP 地址、端口、标记)并将每个数据包分配到相应的类中。
- Action(动作)。数据包匹配后将执行的操作:转发、丢弃、重定向。
这些组件构成了一棵树。数据包从根 qdisc 进入,经过过滤器,由 major:minor 句柄分入不同类,最终在叶节点 qdisc 排队等待传输。
对于比基于端口的匹配更复杂的场景,请在mangle表中使用iptables或nftables对数据包进行标记,然后使用 fw filter tc 中的过滤器,根据标记进行分类。其可扩展性远优于为每种流量类型单独链式连接原始 u32 规则。
出站与入站
方向很重要。内核可以缓冲和延迟出站数据包,这正是实现真正流量整形的关键。当你看到入站数据包时,它们已经通过网络传输,因此除非你先将其重定向到 IFB 设备,否则只能对其进行流量监管(超过阈值时丢弃)。
| 功能 | 出站 | 入站 |
|---|---|---|
| 方向 | 出站 | 入站 |
| 流量整形 | 原生 | 需要IFB |
| 流量监管 | 受支持 | 支持 |
| 典型用途 | QoS、带宽共享、速率控制 | 速率限制、基础DDoS缓解 |
您实际会用到的 qdisc
- HTB(分层令牌桶)。支持类管理。当您希望为每个服务保证最低带宽,同时能够从其他类中借用未使用的带宽时,请使用它。
- TBF(令牌桶过滤器)。无类。当您只需将整个接口的速率限制在单一值时使用。
- fq_codel(公平队列控制延迟)。 结合了按流公平性与主动队列管理,以消除缓冲区膨胀。自 systemd 217 以来,它已成为大多数 Linux 发行版的默认 qdisc,并在 RHEL 9 中作为默认配置提供。请务必将其作为 HTB 类下的叶节点 qdisc 进行挂载,否则单个贪婪的流量流可能会独占整个类。
在 Linux 服务器上配置 tc
tc 随 iproute2 软件包一同提供。在 Debian 和 Ubuntu 上,请使用以下命令进行安装 apt-get install iproute2。在 RHEL 及其衍生系统上, yum install iproute。您需要 root 或 sudo 权限。
首先确认正确的接口名称。接口命名错误是配置文件无响应的最常见原因:
ip link show检查接口上的现有数据,包括实时计数器:
tc -s qdisc show dev eth0在应用新配置前清除任何现有的 root qdisc,以避免 RTNETLINK answers: File exists 错误:
tc qdisc del dev eth0 root 2>/dev/null || true如果您是在更新现有规则而非从头开始,请使用 replace 代替 add 以实现原子替换。
TSO 和 GSO 等硬件卸载功能会以干扰流量整形的方式对数据包进行打包。请在需要整形的接口上将其关闭:
sudo ethtool -K eth0 tso off gso off将 fq_codel 作为新接口的全局默认队列调度器:
sysctl -w net.core.default_qdisc=fq_codel对于繁忙的服务器,建议将其与 BBR 拥塞控制算法(内核 4.9 及以上)配合使用。BBR 能在不增加队列长度的情况下保持高吞吐量:
sysctl -w net.ipv4.tcp_congestion_control=bbr若通过 SSH 配置远程服务器,请养成以下安全习惯:打开第二个会话,并将 tc qdisc del dev eth0 root 准备好粘贴。一条错误的过滤规则可能会让你瞬间被锁在系统之外。
使用 HTB 对出站流量进行整形
HTB 允许您为每项服务设定保证的最低值(rate)和上限(ceil)。未使用的带宽将按优先级顺序分配给需要它的服务。以下是一个针对 1 Gbps 上行链路的有效三层配置。
创建根 HTB 队列调度器。 default 30 会将任何未分类的数据包发送至 class 1:30 ,而非允许其绕过您的规则:
tc qdisc add dev eth0 root handle 1: htb default 30将总吞吐量限制在 900 Mbps。始终将整形值设定在实际链路容量的略低水平,否则队列会在您无法控制的上游路由器或调制解调器上形成:
tc class add dev eth0 parent 1: classid 1:1 htb rate 900mbit ceil 900mbit定义服务层级。优先级较低的 prio 级别的服务优先获得未使用的带宽:
# High priority: web and API traffic
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 500mbit ceil 900mbit prio 1
# Medium priority: database replication
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 300mbit ceil 900mbit prio 2
# Low priority: bulk and backup traffic
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 100mbit ceil 900mbit prio 3将 fq_codel 作为每个类别的叶节点 qdisc,以防止单个流量流独占其层级:
tc qdisc add dev eth0 parent 1:10 handle 10: fq_codel
tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
tc qdisc add dev eth0 parent 1:30 handle 30: fq_codel现在对流量进行分类。对于简单的端口匹配, u32 是最快的:
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip dport 443 0xffff flowid 1:10对于任何需要状态跟踪的情况,请在 iptables 中添加标记,并通过该标记进行匹配 fw:
iptables -t mangle -A OUTPUT -p tcp --dport 5432 -j MARK --set-mark 2
tc filter add dev eth0 protocol ip parent 1:0 prio 2 handle 2 fw flowid 1:20使用 IFB 整形入站流量
您无法原生对入站流量进行整形,因为当数据包到达时,它已经占用了您的带宽。解决方法是将入站流量重定向到中间功能块(IFB)虚拟接口,在内核中,该接口会被视为出站接口,从而允许您应用基于类的队列调度器。
加载模块并启用接口:
modprobe ifb numifbs=1
ip link set dev ifb0 up在物理接口上添加一个入站队列调度器,并将所有流量重定向至 ifb0:
tc qdisc add dev eth0 ingress handle ffff:
tc filter add dev eth0 parent ffff: protocol all u32 \
match u32 0 0 action mirred egress redirect dev ifb0从此时起, ifb0 该接口的行为与其他接口无异。请像处理出站流量一样,向其应用您的 HTB 树:
tc qdisc add dev ifb0 root handle 1: htb default 30
tc class add dev ifb0 parent 1: classid 1:1 htb rate 900mbit ceil 900mbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 500mbit ceil 900mbit prio 1使用 DSCP 进行流量优先级排序
DSCP(差异化服务代码点)会在 TOS 字节中使用 6 位值对数据包进行标记,因此您的 tc 过滤器可以按标签进行分类,而非在规则集中遍历端口。在匹配 DSCP 时 tc,请将该值向左移 2 位。DSCP EF (46) 将变为 0xb8。该掩码 0xfc 将 6 位 DSCP 位与 2 位 ECN 位分离。
针对服务器工作负载的合理默认映射:
| 流量类型 | DSCP | TOS 十六进制 | 示例 |
|---|---|---|---|
| 交互式 | EF | 0xb8 | SSH、DNS、VoIP |
| 企业 | AF41 | 0x88 | HTTP、HTTPS、API |
| 批量 | CS1 | 0x20 | 备份、FTP、软件包更新 |
| 尽力而为 | CS0 | 0x00 | 其他所有 |
在出站数据包到达过滤器之前,先在 iptables 中对其进行标记 tc 过滤器之前,在 iptables 中为其添加标签:
iptables -t mangle -A OUTPUT -p tcp --dport 22 -j DSCP --set-dscp 46然后在 tc 并将其路由到正确的 HTB 类:
# EF (SSH, VoIP) goes to the high-priority class
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip tos 0xb8 0xfc flowid 1:10
# AF41 (web traffic) goes to the medium class
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 \
match ip tos 0x88 0xfc flowid 1:20
# CS1 (bulk) goes to the low-priority class
tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 \
match ip tos 0x20 0xfc flowid 1:30监控与故障排除
您将经常使用的三个命令:
tc -s qdisc show dev eth0
tc -s class show dev eth0
tc -s filter show dev eth0关注 dropped 和 overlimits 计数器。丢包意味着队列已饱和;超限则表示您触及了类上限,内核不得不延迟或丢弃流量。实时查看请用:
watch -n 1 'tc -s class show dev eth0'添加 -d 以查看内部参数(目标、间隔、量子),并 -j 若需将数据管道传输至指标堆栈,请添加以获取 JSON 输出。配合 ss -tin 可查看 TCP 层的 RTT 估计值和重传情况。
大多数故障可归纳为以下几种:
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
RTNETLINK answers: File exists | 根 qdisc 已配置 | tc qdisc del dev eth0 root 首先 |
| 规则生效但流量未受限 | 接口错误,或 TSO/GSO 仍处于启用状态 | 请通过 ip link show,使用 ethtool -K |
| 过滤器从未匹配 | 端口/IP语法错误或子网掩码对齐问题 | 添加一个计数器操作,并在 tc -s filter show |
| 重启后规则消失 | 配置仅驻留于内存中 | 封装在脚本中,并通过 systemd 或 NetworkManager 分发器调用 |
| 优先级流量延迟过高 | 没有叶节点 qdisc,或突发值过低 | 将 fq_codel 连接到叶节点类,提高 burst |
如果您因配置错误而无法访问,重置方法很简单:
tc qdisc del dev eth0 roottc 无法凭空制造本不存在的带宽,但在配置完善的上行链路上,这决定了性能是否可预测,还是服务器会在某个租户启动大文件传输的瞬间崩溃。如果您需要原始带宽并希望自由地按需进行流量整形,不妨关注 FDC 的专用服务器。

Linux 流量控制 (tc):实用指南
使用 tc 在 Linux 上控制带宽、确定流量优先级以及塑造入口和出口。为实际服务器配置 HTB、IFB、DSCP 和 fq_codel。
12 分钟阅读 - 2026年6月5日
为什么必须拥有功能强大且不计量的 VPS
7 分钟阅读 - 2025年5月9日