news 2026/10/20 6:45:55

HFSC分层公平服务曲线:软路由低延迟QoS实战与调优指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HFSC分层公平服务曲线:软路由低延迟QoS实战与调优指南

1. 从一个真实痛点说起:为什么家里的下载一开,游戏就卡成幻灯片

很多人都有过这种体验:明明宽带是500M甚至1000M,但只要家里有人在下载大文件或者看4K视频,自己打游戏就开始疯狂跳ping,视频会议也开始卡顿、声音断续。测速软件跑出来的数字依然漂亮,可实际体验就是一团糟。这个问题困扰了我很久,直到我把家里的软路由从简单的HTB队列换成了HFSC,才真正把这个问题按在地上摩擦。

HFSC,全称Hierarchical Fair Service Curve,翻译过来叫“分层公平服务曲线”。它是Linux流量控制(Traffic Control,简称TC)体系里的一种队列规则(qdisc),专门用来解决“既要保证实时业务的低延迟,又要充分利用剩余带宽”这个看似矛盾的需求。简单说,它能让你的游戏包、语音包永远优先通过,同时又不浪费一丁点带宽给下载和视频流。这篇文章我会把HFSC的原理、配置、调参、踩坑经验全部倒出来,适合有一定Linux网络基础、正在折腾软路由或者服务器QoS的读者。哪怕你只是刚听说TC,我也会用生活化的类比把核心概念讲清楚。

我写这篇东西的起因很简单:网上关于HFSC的中文资料要么太学术,满篇公式;要么太简略,抄来抄去就那几行命令,出了问题根本不知道怎么排查。我前后折腾了大概两个月,把家里和实验室的几台机器都刷了个遍,踩了无数坑,才总结出一套能稳定跑起来的方案。下面这些内容,都是我一行一行命令敲出来、一个包一个包抓出来验证过的。

2. HFSC到底解决了什么问题:从“排队”说起

2.1 网络拥塞的本质:路由器不是高速公路,而是收费站

要理解HFSC,得先理解网络设备怎么处理数据包。你可以把路由器的出口网卡想象成一个单车道收费站,数据包就是排队过站的汽车。当车流量小于收费站的通过能力时,所有车都能直接开过去,没有延迟。但当车流量超过通过能力时,就必须排队。排队本身不可怕,可怕的是排队的规则。

默认情况下,Linux的网卡队列是FIFO(先进先出),谁先到谁先走。这听起来很公平,但实际很糟糕:一个下载大文件的巨大数据包和一个游戏的小数据包同时到达,它们被同等对待。更糟的是,下载会产生大量数据包,把队列塞满,游戏包只能排在后面,延迟瞬间飙升。这就是所谓的“缓冲膨胀”(Bufferbloat)。

HTB(Hierarchical Token Bucket)是另一种常见的解决方案,它通过令牌桶来限制每类流量的速率。但HTB有个问题:它只关心“平均速率”,不关心“延迟”。你可以给游戏流量分配高优先级,但只要下载流量没有超过它的限制速率,HTB就不会主动压制它。结果就是,下载依然会把队列填满,游戏包的延迟还是下不来。

2.2 HFSC的核心思想:给每类流量画一条“服务曲线”

HFSC的突破点在于,它不只看速率,还看“延迟”。它为每个类别定义了一条“服务曲线”(Service Curve),这条曲线描述的是:在任意时间窗口内,这个类别至少能获得多少带宽。这条曲线由两个参数决定:速率(rate)和延迟(delay)。

举个生活化的例子。假设你是一个餐厅经理,餐厅有两个窗口:一个给普通顾客,一个给VIP。HTB的做法是:给VIP窗口预留2个服务员,普通窗口用剩下的。但如果普通顾客突然暴增,把普通窗口的队伍排到了门外,VIP顾客虽然不用排那么久,但整个餐厅的秩序还是乱的。HFSC的做法是:给VIP顾客一个承诺——“你来了之后,最多等30秒就能吃上饭”。为了兑现这个承诺,餐厅必须动态调整服务员的分配,哪怕普通窗口排长队,也要保证VIP窗口的承诺不被打破。

这就是HFSC的“实时”(realtime)曲线。它保证的是延迟上限,而不是平均速率。对于游戏、VoIP这类对延迟极度敏感的业务,这个特性简直是救命稻草。

2.3 为什么选HFSC而不是HTB或CAKE

你可能会问,现在不是有CAKE了吗?CAKE确实很优秀,它集成了流量整形、公平队列和主动队列管理,配置也简单。但CAKE的公平队列是“流”级别的,它不区分业务类型。也就是说,CAKE会把你的游戏流和下载流同等对待,只是保证每条流公平。如果你的游戏流和下载流数量差不多,CAKE确实能缓解问题。但如果你开了几十个下载连接,游戏流依然会被淹没。

HFSC的优势在于“分层”和“分类”。你可以精确地定义:游戏流量属于哪个类,享受什么服务曲线;下载流量属于哪个类,被限制在什么范围。这种精细控制是CAKE做不到的。当然,HFSC的配置复杂度也远高于CAKE,这就是取舍。

我个人的选择是:在核心出口用HFSC做精细分类,在内部接口用CAKE做简单整形。两者结合,效果最好。

3. 核心概念拆解:SC、RT、LS与分层结构

3.1 服务曲线(Service Curve)的两种模式

HFSC的每个类都可以定义两条服务曲线:实时曲线(Realtime,简称RT)和链路共享曲线(Link Sharing,简称LS)。这两条曲线可以单独使用,也可以同时使用。

实时曲线保证的是“延迟上限”。它的参数是m1、d、m2。m1是初始速率,d是延迟时间,m2是后续速率。这条曲线描述的是:在d时间之后,这个类至少能获得m2的速率。如果m1等于m2,那就退化成一条简单的速率保证。对于游戏流量,我通常设置m1等于m2,d设得很小,比如5ms,这样游戏包几乎不用排队。

链路共享曲线保证的是“带宽分配比例”。它的参数是m2,也就是这个类在链路空闲时能借用的最大速率。当链路拥塞时,所有类按照各自的m2比例分配带宽。这有点像HTB的ceil,但更灵活。

注意:实时曲线和链路共享曲线不能同时设置m1和m2冲突的参数。如果同时设置,实时曲线优先。我建议对延迟敏感的类只设实时曲线,对吞吐量敏感的类只设链路共享曲线。

3.2 分层结构:像文件夹一样管理流量

HFSC最强大的地方在于“分层”。你可以创建一个根类,然后在下面创建子类,子类下面再创建子类。每个子类都可以有自己的服务曲线。这种树状结构让你可以非常清晰地组织流量。

比如,我可以这样设计:

  • 根类(1:0):总带宽100Mbit
    • 交互类(1:1):游戏、VoIP,实时曲线5Mbit,延迟5ms
    • 流媒体类(1:2):视频会议、在线视频,链路共享30Mbit
    • 下载类(1:3):HTTP下载、P2P,链路共享60Mbit
      • 大文件下载(1:31):链路共享40Mbit
      • 小文件下载(1:32):链路共享20Mbit

这种结构的好处是,当下载类整体带宽没用满时,流媒体类可以借用;当流媒体类也没用满时,交互类可以借用。但交互类的实时曲线始终被保证,不受其他类影响。

3.3 分类器(Classifier):怎么把包分到对应的类

定义了类之后,还需要告诉HFSC怎么把数据包分到这些类里。这就是分类器的工作。Linux TC支持多种分类器:u32、fw、flow、cgroup等。

我最常用的是fw分类器,配合iptables的MARK目标。因为iptables的匹配规则非常灵活,可以基于端口、IP、协议、甚至连接状态来打标记。比如:

# 给游戏包打标记1 iptables -t mangle -A POSTROUTING -p udp --dport 27015 -j MARK --set-mark 1 # 给视频会议包打标记2 iptables -t mangle -A POSTROUTING -p udp --dport 3478 -j MARK --set-mark 2 # 给下载包打标记3 iptables -t mangle -A POSTROUTING -p tcp --dport 443 -j MARK --set-mark 3

然后在TC里用fw分类器把标记映射到类:

tc filter add dev eth0 parent 1: protocol ip handle 1 fw flowid 1:1 tc filter add dev eth0 parent 1: protocol ip handle 2 fw flowid 1:2 tc filter add dev eth0 parent 1: protocol ip handle 3 fw flowid 1:3

这种方式的优点是逻辑清晰,iptables规则和TC规则分离,方便维护。缺点是iptables的mangle表会消耗一些CPU,但在家用场景下完全可以接受。

提示:如果你用的是nftables,可以用meta mark set来替代iptables的MARK目标,性能更好,语法也更现代。

4. 手把手配置:从零搭建一个可用的HFSC规则集

4.1 环境准备与前置检查

在开始之前,你需要确认几件事:

  1. 你的内核编译了HFSC模块。大多数发行版默认都有,可以用modprobe sch_hfsc测试。如果没有报错,就说明支持。
  2. 你的网卡支持多队列。如果是单队列网卡,HFSC依然能用,但效果可能打折扣。可以用ethtool -l eth0查看队列数。
  3. 你有一台可以做路由的Linux机器,比如软路由、树莓派或者云服务器。

我建议先在虚拟机或者测试环境里跑一遍,确认没问题再上生产。因为TC规则一旦配错,可能导致网络中断,远程SSH会直接断掉。如果你是在远程服务器上操作,一定要先设置一个定时任务,比如at now + 5 minutes来清空规则,防止把自己关在门外。

# 安全措施:5分钟后自动清空TC规则 echo "tc qdisc del dev eth0 root" | at now + 5 minutes

4.2 清空现有规则与创建根队列

第一步永远是清空现有规则,避免冲突:

tc qdisc del dev eth0 root 2>/dev/null

然后创建HFSC根队列:

tc qdisc add dev eth0 root handle 1: hfsc default 3

这里的default 3表示,如果没有匹配到任何分类器的包,默认送到类1:3。这个默认类很重要,它相当于“垃圾桶”,所有未分类的流量都会走这里。我通常把默认类设为下载类,因为大部分未分类流量都是普通上网流量。

4.3 定义根类与子类

接下来定义根类。根类通常不设限制,只是作为树的根:

tc class add dev eth0 parent 1: classid 1:1 hfsc sc rate 100mbit ul rate 100mbit

等等,这里有个常见的误区。很多人以为根类要设成总带宽,其实不对。根类应该是一个“容器”,它的速率应该等于或略大于你的实际出口带宽。如果你的宽带是100Mbit,上行可能只有20Mbit,那么你应该按上行带宽来设置。因为QoS主要控制的是发送方向,也就是上行。

假设我的上行是20Mbit,那么根类应该这样设:

tc class add dev eth0 parent 1: classid 1:1 hfsc sc rate 20mbit ul rate 20mbit

然后创建三个子类:

# 交互类:实时曲线,保证5ms延迟,速率2Mbit tc class add dev eth0 parent 1:1 classid 1:10 hfsc rt m1 2mbit d 5ms m2 2mbit ls rate 2mbit # 流媒体类:链路共享,保证8Mbit tc class add dev eth0 parent 1:1 classid 1:20 hfsc ls rate 8mbit # 下载类:链路共享,保证10Mbit tc class add dev eth0 parent 1:1 classid 1:30 hfsc ls rate 10mbit

这里解释一下参数:

  • rt m1 2mbit d 5ms m2 2mbit:实时曲线,初始速率2Mbit,延迟5ms,后续速率2Mbit。这意味着交互类在任何5ms窗口内都能获得至少2Mbit的带宽,延迟极低。
  • ls rate 8mbit:链路共享曲线,保证8Mbit。当链路空闲时,可以借用更多。
  • ls rate 10mbit:下载类保证10Mbit。

注意,这三个类的保证速率加起来是2+8+10=20Mbit,正好等于总带宽。这是故意的,这样可以确保每个类都有保底带宽,不会互相饿死。

4.4 为每个类挂载叶子队列

类本身只是逻辑概念,真正排队的是叶子队列。HFSC的类下面需要挂一个叶子qdisc,通常是fq_codel或sfq。我强烈推荐fq_codel,因为它能有效对抗缓冲膨胀,而且几乎不需要调参。

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

fq_codel的全称是Fair Queueing with Controlled Delay,它会把每个流单独排队,并且主动丢弃那些排队时间过长的包。这样即使某个类内部有多个流,也不会互相影响。

4.5 配置分类器与iptables标记

现在到了最关键的一步:把数据包分到对应的类。我前面说了用fw分类器,这里给出完整的iptables规则。

假设我的内网网段是192.168.1.0/24,出口网卡是eth0。我要做的是:

  1. 游戏流量:UDP端口27000-27100,标记1
  2. 视频会议:UDP端口3478-3480,标记2
  3. 其他所有流量:标记3
# 清空mangle表 iptables -t mangle -F iptables -t mangle -X # 游戏流量 iptables -t mangle -A POSTROUTING -o eth0 -p udp --dport 27000:27100 -j MARK --set-mark 1 # 视频会议 iptables -t mangle -A POSTROUTING -o eth0 -p udp --dport 3478:3480 -j MARK --set-mark 2 # 其他所有流量 iptables -t mangle -A POSTROUTING -o eth0 -j MARK --set-mark 3

然后在TC里添加过滤器:

tc filter add dev eth0 parent 1: protocol ip handle 1 fw flowid 1:10 tc filter add dev eth0 parent 1: protocol ip handle 2 fw flowid 1:20 tc filter add dev eth0 parent 1: protocol ip handle 3 fw flowid 1:30

注意:iptables的MARK是在POSTROUTING链打的,而TC的过滤器是在出口方向生效的。这个顺序是正确的。如果你在PREROUTING链打标记,TC可能看不到,因为包还没经过路由决策。

4.6 验证配置是否生效

配置完成后,用以下命令检查:

tc qdisc show dev eth0 tc class show dev eth0 tc filter show dev eth0

你应该能看到类似这样的输出:

qdisc hfsc 1: root refcnt 2 default 30 qdisc fq_codel 10: parent 1:10 qdisc fq_codel 20: parent 1:20 qdisc fq_codel 30: parent 1:30 class hfsc 1:1 parent 1: sc rate 20Mbit ul rate 20Mbit class hfsc 1:10 parent 1:1 rt m1 2Mbit d 5.0ms m2 2Mbit ls rate 2Mbit class hfsc 1:20 parent 1:1 ls rate 8Mbit class hfsc 1:30 parent 1:1 ls rate 10Mbit

然后可以用ping和iperf3来测试。一边跑iperf3打满下载,一边ping网关,看延迟是否稳定。如果延迟从1ms飙升到100ms,说明配置有问题。如果延迟稳定在5ms以内,恭喜你,成功了。

5. 参数调优与实战经验:那些文档不会告诉你的细节

5.1 实时曲线的d值到底设多少合适

d值是实时曲线的延迟参数,它决定了这个类最多能容忍多少延迟。设得太小,比如1ms,会导致HFSC过于激进地调度,可能影响其他类;设得太大,比如50ms,又起不到低延迟的效果。

我的经验是:对于游戏流量,d设5ms到10ms比较合适。因为大多数游戏服务器的tickrate是64或128,对应15.6ms或7.8ms的间隔。5ms的延迟保证足够覆盖。对于VoIP,d可以设10ms到20ms,因为语音对延迟的容忍度稍高,但抖动更敏感。

实操心得:你可以先用d 10ms跑一段时间,然后用mtr或者ping -f观察延迟分布。如果延迟经常超过10ms,说明d设小了,需要调大或者增加m1。

5.2 链路共享曲线的rate怎么分配

链路共享曲线的rate决定了拥塞时各类能分到多少带宽。分配原则是:保证关键业务的最低需求,剩余带宽按比例分给其他业务。

我通常的做法是:

  1. 先算出总上行带宽,比如20Mbit。
  2. 给交互类分配2Mbit,这是游戏和VoIP的最低需求。
  3. 给流媒体类分配8Mbit,这是视频会议的最低需求。
  4. 剩下的10Mbit给下载类。

这样分配的好处是,即使下载类跑满,流媒体和交互类依然有保底带宽。而且当流媒体类空闲时,下载类可以借用它的带宽,提高整体利用率。

5.3 分层结构的深度与复杂度权衡

HFSC支持多层嵌套,但层数越多,CPU开销越大,配置也越复杂。我建议最多三层:根类、业务类、子业务类。再深就不划算了。

比如,你可以在下载类下面再分“大文件下载”和“小文件下载”,但没必要再分“HTTP下载”和“FTP下载”。因为fq_codel已经在叶子队列层面做了流级别的公平调度,再细分意义不大。

5.4 常见问题速查表

问题现象可能原因排查方法解决方案
配置后网络完全不通根类速率设错,或者默认类没设tc class show检查根类速率确保根类速率等于实际带宽,默认类存在
游戏延迟依然很高实时曲线参数不对,或者分类器没匹配到tc -s filter show查看匹配计数调整d值,检查iptables标记
下载速度上不去链路共享速率设太低,或者根类速率太小tc -s class show查看各队列统计调大下载类的rate,检查根类速率
CPU占用过高分类器太复杂,或者包量太大top查看ksoftirqd占用简化分类器,用fw替代u32
延迟忽高忽低fq_codel参数不合适tc -s qdisc show查看丢包统计调整fq_codel的limit和target

5.5 独家避坑技巧

技巧一:先用tc -s观察,再调参。很多人配完就不管了,其实tc -s class show dev eth0会显示每个类的包计数、字节数、丢包数。如果某个类的丢包数很高,说明它的速率设小了,需要调大。

技巧二:用iftop或nethogs确认流量分类是否正确。有时候iptables规则写错了,流量跑到了错误的类里,但TC不会报错。用iftop看实时流量,再对照tc -s的计数,就能发现。

技巧三:不要忽略上行和下行。HFSC只能控制发送方向,也就是上行。如果你要控制下行,需要在ISP的入口做整形,或者用IFB(Intermediate Functional Block)做重定向。IFB的配置比较复杂,我一般只在必要时才用。

技巧四:定期清理iptables规则。如果你经常改规则,iptables的mangle表会积累很多无用规则,影响性能。建议每次修改前先iptables -t mangle -F清空。

6. 进阶玩法:HFSC与CAKE、IFB的组合拳

6.1 用IFB控制下行流量

前面说了,HFSC只能控制上行。但很多时候,下行流量才是问题所在。比如你下载一个文件,下行流量把队列塞满,导致游戏延迟飙升。这时候就需要IFB。

IFB的工作原理是:把下行流量重定向到一个虚拟接口(ifb0),然后在ifb0上做整形,再把流量送回原接口。这样就能控制下行方向了。

配置步骤:

# 加载ifb模块 modprobe ifb numifbs=1 # 启用ifb0 ip link set dev ifb0 up # 把eth0的下行流量重定向到ifb0 tc qdisc add dev eth0 handle ffff: ingress tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0 # 在ifb0上配置HFSC tc qdisc add dev ifb0 root handle 1: hfsc default 30 # ... 后续配置和eth0类似

注意:IFB会增加CPU开销,因为每个包都要被重定向一次。在千兆以下的环境里问题不大,但万兆环境要谨慎。

6.2 HFSC与CAKE的混合部署

CAKE在流级别公平调度上做得很好,但缺乏业务分类能力。我的做法是:在出口用HFSC做业务分类,在每个类的叶子队列用CAKE替代fq_codel。

tc qdisc add dev eth0 parent 1:10 handle 10: cake bandwidth 2mbit tc qdisc add dev eth0 parent 1:20 handle 20: cake bandwidth 8mbit tc qdisc add dev eth0 parent 1:30 handle 30: cake bandwidth 10mbit

这样既有HFSC的业务分类,又有CAKE的流级别公平和主动队列管理。实测下来,游戏延迟比纯fq_codel还要低1-2ms。

6.3 动态调整:用脚本根据时间切换规则

如果你有定时任务,比如晚上下载,白天办公,可以写一个脚本,根据时间切换HFSC规则。比如:

#!/bin/bash HOUR=$(date +%H) if [ $HOUR -ge 9 ] && [ $HOUR -le 18 ]; then # 工作时间:限制下载 tc class change dev eth0 parent 1:1 classid 1:30 hfsc ls rate 5mbit else # 非工作时间:放宽下载 tc class change dev eth0 parent 1:1 classid 1:30 hfsc ls rate 15mbit fi

这个脚本可以放在crontab里,每小时执行一次。tc class change命令可以动态修改类的参数,不需要重建整个队列。

7. 我的个人体会与后续扩展方向

折腾HFSC这段时间,我最大的感受是:网络QoS不是一劳永逸的事情,而是一个持续调优的过程。不同的网络环境、不同的业务组合、甚至不同的时间段,最优参数都可能不一样。我一开始照搬网上的配置,结果游戏延迟反而更高了,后来才发现是d值设得太小,导致HFSC过于频繁地调度,增加了CPU开销和抖动。

另一个体会是,不要迷信“最优配置”。网上很多配置是针对特定场景的,比如千兆对称光纤、或者特定运营商的网络。你的环境不一样,参数就要调整。我建议先用保守的参数跑起来,然后用tc -s和ping观察,逐步微调。

后续我打算尝试几个方向:一是用eBPF替代iptables做分类,性能更好,规则也更灵活;二是把HFSC和SDN结合,实现集中式的QoS策略下发;三是研究HFSC在容器网络里的应用,比如Kubernetes的CNI插件怎么利用HFSC做Pod级别的带宽保证。

最后分享一个小技巧:如果你不确定某个类的参数是否合适,可以先用tc class change临时改一下,观察效果,确认没问题再写进启动脚本。这样比反复重启网络服务要快得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:45:39

大模型+语音交互破解老年教育困局:AI助教落地实战复盘

过去几个月我一直在做一个社区数字化教育的评估项目,旁听了好几节面向老年人的智能手机课。课堂上的真实状态跟很多人想象的“老人不愿学”完全相反——教室里坐得很满,老人们带着本子来做笔记,认真程度比不少年轻人都高。可问题也极其突出&a…

作者头像 李华
网站建设 2026/10/20 6:44:40

家居高潜流失顾客识别与AI Agent转化策略实战

1. 高潜流失顾客到底该怎么定义1.1 从"下单率提升104%"倒推问题本质看到"下单率提升104%"这个数字,很多人的第一反应是"翻了一倍多,怎么做到的"。但做过家居行业运营的人都知道,家居品类的下单决策链路跟快消品…

作者头像 李华
网站建设 2026/10/20 6:44:20

PPT-Master:用代码批量生成可维护的PowerPoint演示文稿

1. 项目概述:这不是一个PPT模板库,而是一套可编程的幻灯片生成引擎“ppt-master”这个词最近在技术圈和内容创作社区里悄悄冒头,不是指某款新出的PPT软件,也不是某个网红模板合集,而是一个正在被越来越多独立开发者、教…

作者头像 李华
网站建设 2026/10/20 6:44:17

FontAwesome Pro 5.15.1 Web版深度解析:结构、加载与生产避坑指南

简介:本资源为 FontAwesome Pro 5.15.1 Web 版官方图标库压缩包,面向前端开发工程师、UI 设计师及 Web 项目构建者,解决网页中高质量、可定制化矢量图标的快速集成与统一管理问题。压缩包体积 22.25MB,包含字体文件(.w…

作者头像 李华
网站建设 2026/10/20 6:44:14

Agent记忆实战:从上下文窗口到分层记忆架构

2. 上下文窗口不等于记忆:一个常被误读的"常识"过去一年里,我见过不少团队在大模型应用上栽跟头,其中一大半的问题都出在对"记忆"的理解上。大家把上下文窗口从8K换到128K甚至1M,以为窗口大了,Age…

作者头像 李华