Linux内核TC(Traffic Control)流量控制深度实战:从HTB队列到CAKE拥塞控制的艺术
引言
在现代网络环境中,无论是家庭宽带的Bufferbloat治理、数据中心的多租户QoS保障,还是边缘网关的带宽分配策略,Linux内核的Traffic Control(TC)子系统都是不可或缺的核心工具。然而,TC因其复杂的层级结构、晦涩的术语和繁多的队列规则,被许多运维工程师视为"黑魔法"。本文将从零开始,深入剖析TC子系统的架构原理,并通过实战案例讲解HTB、FQ_CODEL、CAKE三大核心组件的配置与优化。
一、TC子系统架构全景
1.1 数据流路径
理解TC的关键在于理解数据包在内核中的流转路径。当数据包从网卡(NIC)到达后,经过hard_start_xmit()进入网络层的dev_queue_xmit(),然后入队到排队规则(qdisc),最终由设备驱动发送出去。整个过程中,TC负责在入队(ingress)和出队(egress)两个方向上进行流量整形。
┌──────────────────────────────────────────────────────────────┐
│ 数据包发送路径 │
│ │
│ 应用层 → Socket → 传输层(TCP/UDP) → 网络层(IP) │
│ │ │
│ ▼ │
│ 邻居子系统(ARP) │
│ │ │
│ ▼ │
│ TC Egress (出队) │
│ ┌─────────────┐ │
│ │ Root Qdisc │ │
│ │ (HTB/FQ) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ Class A Class B Class C │
│ (子队列) (子队列) (子队列) │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Leaf Qdisc Leaf Qdisc Leaf Qdisc │
│ (FQ_CODEL) (FQ_CODEL) (SFQ) │
│ │ │ │
│ └────────────┘ │
│ │ │
│ dev_hard_start_xmit() │
│ ▼ │
│ 网卡发送 │
└──────────────────────────────────────────────────────────────┘
1.2 核心概念术语表
在深入实战之前,先建立一个清晰的概念坐标系:
| 术语 | 说明 |
|---|---|
| Qdisc | 排队规则(Queueing Discipline),控制数据包排队和出队算法 |
| Class | 类(仅classful qdisc支持),用于树形分层结构的带宽分配 |
| Filter | 分类器,将数据包分流到指定的Class |
| Classifier | 分类算法(如u32、bpf、flow),匹配数据包特征 |
| Shaping | 整形,缓存超额流量以符合速率限制(可延迟发送) |
| Policing | 监管,直接丢弃超额流量(ingress只能policing) |
| CBQ | Class Based Queueing,早期的基于类的队列 |
| HTB | Hierarchy Token Bucket,层次令牌桶,最常用的classful qdisc |
| SCHED_PRIO | 优先级队列,简单的3-band优先级调度 |
1.3 TC命令结构
TC命令遵循一致的结构:tc [OBJECT] [COMMAND] dev [IFACE] [PARAMETERS]
# 查看现有qdisc配置
tc qdisc show dev eth0
# 查看分类统计
tc -s class show dev eth0
# 查看过滤器规则
tc filter show dev eth0
# 查看特定队列统计
tc -s qdisc show dev eth0
二、HTB(层次令牌桶):分层带宽分配的基石
2.1 令牌桶算法原理
HTB的核心思想是令牌桶(Token Bucket):系统以固定速率生成令牌,每个令牌代表发送一定字节数据的权限。数据包必须消耗对应数量的令牌才能被发送。当令牌耗尽时,数据包必须等待新令牌生成(整形)或直接丢弃(监管)。
HTB引入了两个关键参数:
- rate:保证带宽(Committed Rate, CR),每秒分配的令牌速率
- ceil:峰值带宽(Ceiling Rate, CR),允许突发时的最大借取带宽
- burst:桶深度,允许一段时间不发送数据后积累的最大令牌数
- cburst:ceil桶深度,用于突发传输的令牌积累
当某个Class的rate未用完时,多余的带宽可以"借给"其他需要带宽的Class(在不超过其ceil的前提下)。这种带宽借贷机制是HTB相比简单限速方案的核心优势。
令牌桶工作机制:
┌─────────────────────────────────────────────────┐
│ 时间 → T=0 T=1 T=2 T=3 T=4 │
│ 令牌生成: +10 +10 +10 +10 +10 │
│ │
│ Case A: 恰好满速率发送 │
│ 消耗: -10 -10 -10 -10 -10 │
│ 桶深度: 0 0 0 0 0 │
│ │
│ Case B: T=1,T=2空闲 积累令牌 │
│ 消耗: -10 0 0 -20 -10 │
│ 桶深度: 0 +10 +20 0 0 │
│ │
│ Case C: 突发超出桶深度 │
│ 消耗: -10 -20 0 -10 -10 │
│ 桶深度: 0 →等待令牌 -10 -10 -10 │
│ (缓冲区等待整形) │
└─────────────────────────────────────────────────┘
2.2 HTB Class层级结构
HTB支持树形层级结构,每个Class可以有子Class。根Class连接到一个默认的qdisc,子Class可以有自己的leaf qdisc。典型的三层结构:根Class → 多个部门Class → 各部门内部的子Class。
HTB层级结构示例:
Root (1:)
rate=1G ceil=1G
│
┌────────┼─────────┐
│ │ │
1:1 1:2 1:3
研发部 市场部 默认
rate=500M rate=300M rate=100M
ceil=800M ceil=500M ceil=1G
│ │
┌───┴───┐ ...
│ │
1:10 1:11
前端组 后端组
rate rate
200M 300M
ceil ceil
400M 500M
三、FQ_CODEL:公平队列与智能拥塞控制的完美结合
3.1 CODEL算法:基于延迟的AQM
FQ_CODEL是Linux内核默认推荐的qdisc,它结合了Fair Queuing(FQ,公平队列)和Controlled Delay(CODEL,受控延迟)两大技术。CODEL解决的是Bufferbloat问题——网络设备中过大的缓冲区导致排队延迟从毫秒级暴涨到秒级。
CODEL的核心参数:
- target:目标排队延迟(默认5ms)。如果某个流的排队延迟超过target,CODEL认为该流占用了过多带宽
- interval:滑动窗口(默认100ms)。CODEL不会在一个interval内连续丢包,避免过度惩罚
CODEL的状态机逻辑:
CODEL状态机:
┌─────────────────────────────────────────────┐
│ 1. 计算当前所有流的排队延迟(sojourn time) │
│ 2. 如果延迟 < target → 状态OK,继续转发 │
│ 3. 如果延迟 > target → 进入DROPPING状态 │
│ 3a. 选择"最差流"(延迟最高的流) │
│ 3b. 向该流发送一个包减少信号 │
│ 3c. 记录drop_next时间 = now + control_func │
│ 4. 后续包根据drop_next判断是否继续drop │
│ - 如果延迟仍高 → drop并更新drop_next │
│ - 如果延迟恢复正常 → 退出DROPPING状态 │
│ │
│ control_next = interval / sqrt(drop_count) │
│ (连续丢包时,间隔缩短,惩罚加速) │
└─────────────────────────────────────────────┘
3.2 公平队列(FQ)调度
FQ将不同流(flow)的数据包分配到不同的子队列,按轮询(Round-Robin)方式确保每个流获得公平的带宽份额。这里的"流"由五元组(src IP, dst IP, src Port, dst Port, Protocol)或flow hash决定。
FQ还实现了流优先级队列:活跃少于5个包的流进入"新流队列"(New Flow Queue)优先发送,老流进入"旧流队列"(Old Flow Queue)。这确保了短HTTP请求、DNS查询等不会被大文件下载阻塞。
四、CAKE:家庭路由器终极队列算法
4.1 CAKE的设计哲学
CAKE(Common Applications Kept Enhanced)是为解决家庭网络Bufferbloat问题设计的综合性AQM/qdisc。它融合了最先进的流量整形、智能拥塞控制和流量分类能力,被誉为"家庭路由器的最佳默认配置"。
CAKE的核心特性:
- 自动带宽检测(ACK Filter):自动给予TCP ACK包最高优先级,避免上行带宽饱和时反向ACK被阻塞,导致下行性能骤降
- 多流公平(Flow Isolation / Host Fairness):支持按源IP/目的IP隔离流,防止单一设备垄断带宽
- 内置SQM(Smart Queue Management):整合CODEL、FQ和HTB的优点
- 支持 ack-filter 和 atm/vc 补偿模式,适配ADSL/VDSL等链路环境
4.2 CAKE的参数调优
# 查看CAKE可用参数
tc qdisc add dev eth0 root cake bandwidth 100mbit
# 常用参数组合
cake [bandwidth RATE] # 链路带宽(必须精确设置)
cake [ack-filter] # 自动优先ACK包(推荐启用)
cake [dual-srchost] # 基于源IP的流隔离
cake [dual-dsthost] # 基于目的IP的流隔离
cake [nat] # 启用NAT感知(根据连接跟踪匹配)
cake [docsis] # DOCSIS链路模式(补偿FEC开销)
cake [pppoe-ptm] # PPPoE + 分片补偿
cake [atm] # ATM信元补偿(53字节/信元)
cake [mpu N] # 最小分组单元大小
cake [ethernet] # 默认以太网帧开销
cake [raw] # 无链路开销补偿
五、实战案例:完整的家庭网关QoS配置
5.1 场景分析
假设一个典型的家庭宽带场景:
- 下行带宽:500Mbps,上行带宽:50Mbps
- 需要保障:工作电脑 > 视频会议设备 > 普通设备 > IoT设备
- 目标:消除Bufferbloat,游戏延迟低于20ms,视频会议不卡
5.2 出站流量配置(eth0 为WAN口)
#!/bin/bash
# ============================================
# 家庭网关QoS出站配置(eth0下行)
# ============================================
WAN="eth0"
DOWNLINK="500mbit"
UPLINK="50mbit"
# 清除旧配置
tc qdisc del dev $WAN root 2>/dev/null
# ========== 出站方向(eth0出队,控制上传) ==========
tc qdisc add dev $WAN root handle 1: htb default 40
# 根类 - 总带宽限制
tc class add dev $WAN parent 1: classid 1:1 htb rate $UPLINK ceil $Uplink
# 1:10 高优先级 - 视频会议(Zoom/Teams/TcMeet)
tc class add dev $WAN parent 1:1 classid 1:10 htb \
rate 15mbit ceil $Uplink burst 15k cburst 15k prio 1
tc qdisc add dev $WAN parent 1:10 handle 10: \
cake bandwidth 20mbit ack-filter nat dual-srchost
# 1:20 中优先级 - 网页浏览与普通下载
tc class add dev $WAN parent 1:1 classid 1:20 htb \
rate 20mbit ceil $Uplink burst 15k cburst 15k prio 2
tc qdisc add dev $WAN parent 1:20 handle 20: \
cake bandwidth 25mbit ack-filter dual-srchost
# 1:30 低优先级 - BT下载/更新包
tc class add dev $WAN parent 1:1 classid 1:30 htb \
rate 5mbit ceil $Uplink burst 30k cburst 30k prio 4
tc qdisc add dev $WAN parent 1:30 handle 30: \
cake bandwidth 10mbit ack-filter
# 1:40 默认类 - 其他流量
tc class add dev $WAN parent 1:1 classid 1:40 htb \
rate 10mbit ceil $Uplink burst 15k cburst 15k prio 3
tc qdisc add dev $WAN parent 1:40 handle 40: \
cake bandwidth 15mbit ack-filter dual-srchost
# ========== 分类器(使用u32匹配端口) ==========
# 视频会议类
tc filter add dev $WAN parent 1:0 protocol ip prio 1 u32 \
match ip dport 3478 0xffff flowid 1:10 # STUN
tc filter add dev $WAN parent 1:0 protocol ip prio 1 u32 \
match ip dport 8801 0xff00 flowid 1:10 # Zoom
tc filter add dev $WAN parent 1:0 protocol ip prio 1 u32 \
match ip dport 5004 0xfffe flowid 1:10 # Teams media
# 默认类
tc filter add dev $WAN parent 1:0 protocol ip prio 100 \
match ip src 0.0.0.0/0 flowid 1:40
5.3 入站流量配置(下载方向使用Ingress Policing)
由于入队流量无法直接整形(包已经到达),需要使用ifb(Intermediate Functional Block)设备进行反向重定向,在ifb上为入站流量创建出站队列。
# ============================================
# 入站流量QoS(通过ifb设备将下载流量变成可控的出队)
# ============================================
# 加载ifb模块
modprobe ifb numifbs=1
ip link set dev ifb0 up
# 将eth0的ingress流量重定向到ifb0的egress
tc qdisc add dev $WAN ingress
tc filter add dev $WAN parent ffff: protocol ip prio 1 \
u32 match u32 0 0 action mirred egress redirect dev ifb0
# 在ifb0上创建HTB + FQ_CODEL
tc qdisc add dev ifb0 root handle 2: htb default 40
tc class add dev ifb0 parent 2: classid 2:1 htb rate $DOWNLINK ceil $DOWNLINK
# 多优先级类(复用出站配置的结构)
tc class add dev ifb0 parent 2:1 classid 2:10 htb \
rate 150mbit ceil $DOWNLINK burst 100k cburst 100k prio 1
tc qdisc add dev ifb0 parent 2:10 handle 10: \
cake bandwidth 150mbit ack-filter dual-dsthost
tc class add dev ifb0 parent 2:1 classid 2:20 htb \
rate 200mbit ceil $DOWNLINK burst 100k cburst 100k prio 2
tc qdisc add dev ifb0 parent 2:20 handle 20: \
cake bandwidth 250mbit ack-filter dual-dsthost
tc class add dev ifb0 parent 2:1 classid 2:30 htb \
rate 50mbit ceil $DOWNLINK burst 200k cburst 200k prio 4
tc qdisc add dev ifb0 parent 2:30 handle 30: \
cake bandwidth 80mbit ack-filter
tc class add dev ifb0 parent 2:1 classid 2:40 htb \
rate 100mbit ceil $DOWNLINK burst 100k cburst 100k prio 3
tc qdisc add dev ifb0 parent 2:40 handle 40: \
cake bandwidth 120mbit ack-filter dual-dsthost
# 入站分类器
tc filter add dev ifb0 parent 2:0 protocol ip prio 1 u32 \
match ip sport 3478 0xffff flowid 2:10
六、TC BPF:用eBPF实现自定义包分类
6.1 BPF Classifier入门
除了u32传统分类器,TC支持BPF程序实现自定义分类逻辑。相比u32,BPF分类器更灵活、更高效,特别适合复杂的流量分类需求。
// tc_bpf_classifier.c - 自定义分类BPF程序
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include "bpf_helpers.h"
SEC("classifier")
int tc_classify(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return TC_ACT_OK;
__u32 dst_ip = ip->daddr;
// 将游戏服务器IP映射到高优先级类
// 假设游戏服务器IP段 1.2.3.0/24
if ((dst_ip & htonl(0xFFFFFF00)) == htonl(0x01020300)) {
// 需要将skb重定向到高优先级流量控制类
// 实际中通过返回classid实现
// 返回 classid (handle) 1:10 = 0x10000 | 0x10 = 0x10010
// 注意 TC_ACT_OK 会继续处理,需要使用 skb->tc_classid
return TC_ACT_OK; // 简化示例
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
6.2 编译与加载BPF分类器
# 编译BPF程序(使用clang)
clang -O2 -target bpf -c tc_bpf_classifier.c -o tc_bpf.o
# 通过tc命令加载BPF分类器
tc filter add dev eth0 parent 1:0 bpf obj tc_bpf.o \
section classifier flowid 1:10
# 或使用direct-action模式(允许drop/redirect等动作)
tc filter add dev eth0 ingress bpf da obj tc_bpf.o \
section classifier
# 查看已加载的BPF程序
tc filter show dev eth0
七、性能诊断与调优技巧
7.1 Bufferbloat检测方法
Bufferbloat是指在带宽饱和时排队延迟急剧增加的现象。使用flent工具可以精确检测:
# 安装flent
sudo apt install flent
# RRUL测试(Real-Time Response Under Load)
flent rrul -H target_host -t "Bufferbloat测试" -o result.png
# 查看排队延迟统计
tc -s qdisc show dev eth0
# 输出示例(关注backlog和drops):
# qdisc htb 1: root refcnt 9 r2q 10 default 40 direct_packets_stat 0 direct_qlen 1000
# Sent 123456789 bytes 891234 pkt (dropped 42, overlimits 12 requeues 0)
# backlog 23456b 156p requeues 0
#
# 解读:dropped=42表示有42个包因超延迟被丢弃
# backlog=156p表示当前队列中有156个包排队
7.2 CAKE带宽参数精确校准
CAKE的带宽参数必须准确设置,否则会导致带宽浪费或拥塞。校准方法:
# 使用speedtest-cli获取实际带宽
speedtest-cli --simple
# 考虑链路层开销,通常设置为实际测速的95-97%
# 例如测速500Mbps,则设置为:
cake bandwidth 480mbit
# 对于PPPoE链路,增加overhead补偿
cake bandwidth 480mbit ethernet pppoe-ptm mpu 84
# 对于DOCSIS链路
cake bandwidth 480mbit docsis ethernet ack-filter-aggressive
7.3 常见性能问题与解决方案
| 问题现象 | 诊断方法 | 解决方案 |
|---|---|---|
| 视频会议卡顿 | tc -s qdisc show 看高优先级类drops | 确保ACK filter启用,降低总体带宽限制 |
| 游戏延迟波动大 | ping -D host 观察延迟变化 | 启用cake的dual-srchost隔离,避免下载抢占 |
| 限速后总带宽不达标 | 对比有/无TC的speedtest结果 | 降低burst/cburst值,或增大interval |
| 特定网站访问慢 | tc -s filter show 检查分类命中率 | 检查filter规则是否正确匹配目标流量 |
| CPU占用异常高 | top看内核CPU开销 | 减少flow数量(flows参数),使用host-aware模式 |
八、TC与TCP BBR的协同优化
TCP BBR(Bottleneck Bandwidth and RTT)与TC流量控制可以协同工作,达到最佳的网络性能。BBR通过主动测量瓶颈带宽和RTT来调整发送速率,而TC队列管理则确保低排队延迟。
重要的协同点:
- 必须保持BBR pacing rate < TC queue rate,否则BBR会持续填充队列导致BBR无法收敛到低延迟状态
- CAKE/FQ_CODEL将排队延迟控制在5ms以内,为BBR提供准确的RTT测量
- 在HTB中为BBR流分配足够的rate和ceil,避免limiting BBR的探测能力
# 启用BBR
echo "net.core.default_qdisc = fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.conf
sysctl -p
# 配合TC使用时的推荐配置
# TC使用HTB + CAKE,CAKE内部target=5ms
# BBR v2可以更高效地配合AQM工作
九、总结与最佳实践
Linux TC是一个功能强大但配置复杂的子系统。通过本文的学习,你应该掌握以下核心要点:
- HTB用于分层带宽分配 — 创建树形Class结构,rate保障带宽,ceil允许借用
- FQ_CODEL是默认通用qdisc — 消除Bufferbloat,保证流间公平
- CAKE是家庭路由器的最佳选择 — 自动ACK过滤 + 流隔离 + 智能AQM
- 入站方向使用ifb重定向 — 通过中间设备将ingress转为可整形的egress
- BPF分类器实现灵活流量控制 — 用自定义程序替代传统u32 filter
建议在家用OpenWrt或企业级网关中优先使用CAKE qdisc,它涵盖了80%的用例需求。只有在需要复杂的多租户分层带宽分配时,才需要HTB + 自定义Leaf Qdisc的组合方案。最后记住:带宽参数必须精确校准,过高的设置浪费带宽,过低则导致拥塞,这是TC调优的黄金法则。

发表评论 取消回复