网络服务质量(QoS)全解析:从VLAN优先级到Linux tc的流量管控实战
1. 从一次网络卡顿说起:为什么你的流量总被“欺负”?
上个月,公司内部搞了一次视频会议,结果好几个同事的画面卡成了PPT,语音也断断续续。IT部门排查了一圈,发现不是带宽不够,也不是服务器问题。最后抓包分析,发现是市场部那边在同步一个超大文件,把网络带宽给“吃”满了,导致视频会议的流量被挤得没路走。这其实就是典型的网络服务质量问题:不同的应用、不同的数据流,在网络这个“公共马路”上,没有“红绿灯”和“专用车道”,结果就是“大货车”(大流量下载)堵死了“救护车”(实时音视频)的路。
要解决这个问题,就得靠网络中的“交通管制”技术——服务质量(Quality of Service, QoS)。但QoS不是一个单一的命令或开关,它是一套从数据链路层到网络层,再到Linux系统层面的完整体系。今天,我们就来把这套体系彻底拆开揉碎了讲清楚,核心就是四个关键词:VLAN优先级、IP优先级、QoS策略、以及Linux下的终极工具tc命令。无论你是网络工程师,还是运维开发,或者是想优化自家NAS和软路由的极客,理解这套组合拳,都能让你对网络流量的掌控力提升一个维度。
2. VLAN优先级:在二层给数据包“贴标签”
我们常说网络分七层,QoS的“管制”可以从第二层,也就是数据链路层就开始。在以太网环境中,VLAN(虚拟局域网)技术除了用来隔离广播域,其帧格式里还有一个至关重要的字段——802.1Q标签头中的优先级字段,也叫COS(Class of Service)或PCP(Priority Code Point),占3个比特。
2.1 802.1Q标签与优先级位
一个标准的以太网帧,在插入802.1Q标签后,结构会发生变化。标签头包含TPID(标签协议标识,固定为0x8100)和TCI(标签控制信息)。TCI里又包含了12位的VLAN ID和3位的PCP。这3位PCP就是VLAN优先级,取值范围是0-7。
| 目的MAC | 源MAC | 0x8100 (TPID) | PCP(3) | CFI(1) | VID(12) | 类型/长度 | 数据 | FCS |这8个优先级(0-7)在IEEE 802.1p标准中被大致定义了一套推荐映射:
- 0 (Best Effort):默认值,尽力而为。
- 1 (Background):后台流量,如网络备份。
- 2 (Spare):未分配。
- 3 (Excellent Effort):优秀尽力,用于业务量较大的数据。
- 4 (Controlled Load):受控负载,用于流媒体。
- 5 (Video, <100ms latency):视频,延迟小于100ms。
- 6 (Voice, <10ms latency):语音,延迟小于10ms。
- 7 (Network Control):网络控制,如生成树协议、OSPF等协议报文。
注意:这个映射只是推荐,并非强制。实际网络中,交换机的具体行为取决于其硬件芯片和软件实现。比如,有些交换机可能只支持4个或8个硬件队列,那么它内部会将0-7这8个优先级映射到有限的几个硬件队列上。
2.2 配置与实践:在交换机上打标签
VLAN优先级的标记通常发生在网络的入口边缘。比如,连接IP电话的交换机端口,我们会将其信任来自电话的VLAN优先级(通常电话会将自己语音RTP流的帧标记为优先级6)。配置命令因厂商而异,但逻辑相通。
以华为交换机(VRP系统)为例:
# 进入连接IP电话的接口 interface GigabitEthernet 0/0/1 port link-type hybrid # 或access/trunk,根据实际情况 port hybrid pvid vlan 10 # 设置端口默认VLAN port hybrid untagged vlan 10 # 对VLAN 10的帧去掉标签发出(给PC) trust 8021p inner # 信任并依据接收到的帧的802.1p优先级进行内部调度以思科交换机(IOS)为例:
interface GigabitEthernet1/0/1 switchport mode access switchport voice vlan 10 # 为语音数据设置VLAN mls qos trust cos # 信任接口接收到的COS值实操心得:配置trust cos或类似命令是关键。如果不信任,交换机入口会忽略数据包自带的优先级,或者将其重置为默认值(通常是0)。这意味着你后面所有的QoS策略都失去了依据。所以,在规划QoS时,第一步就是确定网络的“信任边界”——从哪里开始,我们相信数据包自带的优先级标签。
3. IP优先级与DSCP:在三层细化“通行证”
数据包进入三层(IP层)后,VLAN的802.1p标签在路由器间传输时通常会被剥离(除非是Q-in-Q等特殊封装)。这时,就需要依靠IP头部的服务类型字段来传递优先级信息。
3.1 ToS字段、IP优先级与DSCP的演进
IPv4包头中有一个1字节的“服务类型(Type of Service, ToS)”字段。历史上,这个字段被解释为两部分:
- IP优先级(IP Precedence):占用高3位,意义与802.1p的PCP类似,取值0-7,常被称为“RFC 791优先级”。
- 延迟、吞吐量、可靠性、成本位:占用中间4位,每个位表示一种服务诉求(D、T、R、C),但实际应用很少。
这种划分方式太粗犷。于是IETF推出了差分服务(Differentiated Services, DiffServ)模型,重新定义了这8位,称为DSCP(Differentiated Services Code Point)。DSCP占用ToS字段的高6位(0-63),低2位保留未用(ECN,显式拥塞通知)。
原ToS字段:| IP Prec (3 bits) | D | T | R | C | 0 | 0 | DiffServ字段:| DSCP (6 bits) | ECN (2 bits) |3.2 常见的DSCP值与PHB
DiffServ定义了一系列“每跳行为(Per-Hop Behavior, PHB)”,并用特定的DSCP值表示:
- CS(Class Selector):为了向后兼容IP优先级,DSCP值
XXX000(即十进制8, 16, 24, 32, 40, 48, 56)对应IP优先级0-7。例如,CS6 (48) 用于网络控制,CS5 (40) 用于语音。 - EF(Expedited Forwarding, 加速转发):DSCP值为46(二进制
101110)。这是为低延迟、低抖动、低丢包率的流量设计的,如VoIP。路由器会为EF流量提供优先处理和保证的带宽。 - AF(Assured Forwarding, 确保转发):定义了4个等级(AF1-AF4),每个等级内有3个丢弃优先级(低、中、高)。格式为
AAADD0。例如:- AF11 (DSCP 10): 用于不太重要的保证数据。
- AF31 (DSCP 26): 用于重要的业务数据。
- AF43 (DSCP 38): 用于重要业务,但在拥塞时丢弃概率最高。
- AF41 (DSCP 34) 常被用于视频流量。
一个实用的对照表:
| 应用类型 | 推荐DSCP值(十进制) | 二进制 | 含义 |
|---|---|---|---|
| 网络控制 (OSPF, BGP) | CS6 (48) | 110000 | 最高优先级,保证路由稳定 |
| 语音 (VoIP) | EF (46) | 101110 | 加速转发,极低延迟 |
| 交互式视频 (视频会议) | AF41 (34) | 100010 | 确保转发,高优先级 |
| 流媒体视频 (点播) | AF31 (26) | 011010 | 确保转发,中高优先级 |
| 关键业务数据 (CRM, ERP) | AF21 (18) | 010010 | 确保转发,中优先级 |
| 尽力而为数据 (Web, Email) | CS0 (0) | 000000 | 默认,无保证 |
| 清道夫流量 (BT下载) | CS1 (8) | 001000 | 最低优先级,带宽空闲时才转发 |
3.3 在路由器/防火墙上标记DSCP
标记行为通常由网络边缘设备(如出口路由器、防火墙)完成,基于ACL(访问控制列表)或NBAR(基于网络的应用识别)来识别流量并打标。
以华为设备(VRP)为例,创建一个流分类,匹配视频会议服务器的流量,并为其标记DSCP为AF41:
acl number 3000 rule 5 permit ip source 192.168.10.100 0 # 视频会议服务器 traffic classifier VIDEO operator or if-match acl 3000 traffic behavior MARK-VIDEO remark dscp af41 # 标记DSCP值为AF41 traffic policy QoS-OUTBOUND classifier VIDEO behavior MARK-VIDEO interface GigabitEthernet 0/0/0 # 出方向接口 traffic-policy QoS-OUTBOUND outbound以思科设备(IOS)为例,使用MQC(模块化QoS命令行)实现类似功能:
access-list 101 permit ip host 192.168.10.100 any class-map match-all VIDEO-CLASS match access-group 101 policy-map MARKING-POLICY class VIDEO-CLASS set dscp af41 interface GigabitEthernet0/0 service-policy output MARKING-POLICY踩坑记录:这里最容易出问题的是标记的位置。一定要在流量离开你的管控域、进入运营商或下一跳网络之前进行标记。如果在内部核心交换机标记,而出口路由器没有相应的信任和队列调度机制,这个标记就白做了。同时,要确保沿途的网络设备都信任DSCP值(trust dscp),否则它们会重写或忽略你的标记。
4. QoS策略模型:管制、整形、队列与丢弃
打好了标签(VLAN PCP或IP DSCP),只是告诉了网络“这是什么类型的流量”。接下来,我们需要在网络设备(主要是路由器和三层交换机)上,根据这些标签来执行具体的“交通规则”。这就是QoS策略的核心:分类、标记、管制、整形、队列、拥塞避免。
4.1 流量管制与流量整形
这两个概念经常被混淆,但它们的方向截然不同。
流量管制(Policing):像一个严厉的交警,发现超速(超过承诺速率)就立刻开罚单(丢弃或降级数据包)。它不缓存数据,对突发流量容忍度低,主要用于限制进入网络的流量速率,保护网络资源。通常用在网络入口。
- 工具:令牌桶算法。桶有固定容量(突发尺寸
bc),以承诺信息速率(cir)向桶中添加令牌。数据包需要拿到令牌才能通过,拿不到就被丢弃或标记为更低优先级。
- 工具:令牌桶算法。桶有固定容量(突发尺寸
流量整形(Shaping):像一个智能的匝道控制器,当主路拥堵时,让车在匝道上排队等候,平滑地放入主路。它会缓存超额的数据包,以平均速率(整形速率)发送,减少丢包,但会增加延迟。主要用于使输出流量符合下游设备的接收能力,避免被下游管制丢弃。通常用在网络出口。
- 工具:也是令牌桶,但多了一个队列(缓存区)。令牌不足时,数据包在队列中等待,而不是直接被丢弃。
配置示例(华为,在接口出方向整形):
interface GigabitEthernet 0/0/1 qos lr outbound cir 10000 # 将出方向流量整形为10Mbps这个命令会平滑该接口的出流量,即使有突发,对外发送的速率也会被限制在10Mbps左右,避免冲击下游设备。
4.2 队列调度机制
当接口发生拥塞(输出队列有数据堆积)时,决定哪个数据包先被发送的规则,就是队列调度。这是影响不同优先级流量体验的关键。
FIFO(先进先出):最简单的队列,所有流量一视同仁,没有QoS。不适合混合流量环境。
PQ(优先队列):定义多个优先级队列(如高、中、普通、低)。高优先级队列不为空时,绝不发送低优先级队列的数据。这保证了高优先级流量的绝对优先,但可能导致低优先级流量“饿死”。
CQ(定制队列):为每个队列分配一个固定的带宽比例(如
queue1 40%,queue2 30%)。按轮询方式从各队列取数据发送,取的数据量与其带宽比例相关。保证了带宽分配,但不够灵活,延迟无法保证。WFQ(加权公平队列):动态的、基于流的公平队列。它会识别不同的“流”(如基于源/目的IP、端口),并为每个流分配一个独立的队列。调度时,根据流的优先级(权重)来分配带宽。低带宽的流也能得到及时服务,防止大流独占资源。它是早期解决“大象流”欺负“老鼠流”的利器。
CBWFQ(基于类的加权公平队列):这是目前企业网最常用的队列机制。它结合了PQ和WFQ的优点。
- LLQ(低延迟队列):这是一个具有严格优先级的队列,通常用于承载EF(语音)流量。只要LLQ有数据,设备就会优先发送LLQ的数据,发送完后才调度其他队列。为了防止LLQ饿死其他流量,需要为LLQ设置一个带宽上限。
- BANDWIDTH队列:其他类别的流量(如AF41视频、AF21业务数据)被分配到不同的BANDWIDTH队列,每个队列被分配一个最小保证带宽(
bandwidth)或占用剩余带宽的比例(bandwidth percent)。 - 默认队列:未分类的流量进入默认队列,通常采用WFQ调度。
配置示例(思科,CBWFQ+LLQ):
class-map match-any VOICE match dscp ef # 匹配语音流量 class-map match-any VIDEO match dscp af41 af42 # 匹配视频流量 policy-map WAN-OUT class VOICE priority 1000 # LLQ,严格优先级,保证带宽1Mbps class VIDEO bandwidth 2000 # 保证带宽2Mbps fair-queue # 队列内使用WFQ class class-default fair-queue # 默认流量使用WFQ bandwidth remaining percent 50 # 占用剩余带宽的50% interface Serial0/0/0 service-policy output WAN-OUT这个策略保证了:1. 语音流量绝对优先且带宽不超过1M;2. 视频流量至少有2M保证带宽;3. 默认流量公平分享剩余带宽。
4.3 拥塞避免:WRED
当队列快满时,是等到全满后“尾丢弃”所有新来的包,还是提前有选择地丢一些包?尾丢弃会导致全局同步(所有TCP连接同时减速然后同时加速,造成网络震荡)。WRED(加权随机早期检测)解决了这个问题。
WRED会监控队列长度,当长度超过某个最低阈值时,开始随机丢弃数据包,丢弃概率随队列长度增加而增加。关键是,WRED可以根据IP优先级或DSCP值设置不同的丢弃阈值。高优先级的流量(如EF)的丢弃阈值设得更高,意味着更不容易被丢弃。
配置思路:通常为AF类流量(特别是AFx3,丢弃优先级高)配置WRED,而对EF(LLQ中的流量)和默认尽力而为流量不配置WRED。因为EF流量需要低延迟,不能缓存太久;尽力而为流量则无所谓。
5. Linux tc命令:在服务器端实现精细化流量控制
前面讲的都是网络设备上的QoS。但在云原生和自建服务的时代,我们经常需要在服务器本身(比如一台运行着Nginx、数据库的Linux服务器)上控制流量,防止某个应用吃光带宽影响其他服务。这就需要祭出Linux内核的强大工具——tc(Traffic Control)。
tc命令功能极其强大,也相对复杂。它通过操作内核中的QDisc(队列规则)、Class(类)和Filter(过滤器)来实现流量控制。
5.1 tc的核心概念与结构
想象一下服务器的一个网络接口(如eth0)的出口流量处理管道:
- QDisc(队列规则):这是管道的总阀门和调度器。每个接口都有一个根QDisc(默认为
pfifo_fast,一个简单的优先级FIFO队列)。我们可以把它替换成更复杂的、支持分类的QDisc,如HTB(分层令牌桶)或CBQ。 - Class(类):存在于可分类的QDisc(如
HTB)内部。我们可以创建多个Class,每个Class代表一个流量类别(如“网页服务”、“数据库同步”、“备份”),并为每个Class分配不同的带宽限制。 - Filter(过滤器):附着在QDisc或Class上,像是一个分类员。它检查每个要通过的数据包(基于IP、端口、协议等),然后决定将这个包送到哪个Class去排队。最常用的过滤器是
u32,通过匹配IP头和数据来分类。 - 叶子QDisc:每个Class下面还可以挂一个QDisc,用于管理该类内部的排队行为,比如用
sfq(随机公平队列)来平滑该类下的多个小流。
一个典型的HTB结构如下:
根QDisc (HTB, 总带宽100Mbps) | |-- Class 1:1 (HTB, 保证速率10M, 最大速率20M) [给SSH流量] | `-- 叶子QDisc (sfq) | |-- Class 1:2 (HTB, 保证速率30M, 最大速率60M) [给Web流量] | `-- 叶子QDisc (sfq) | `-- Class 1:3 (HTB, 保证速率10M, 最大速率不限) [默认类] `-- 叶子QDisc (sfq)过滤器将去往22端口的流量导向Class 1:1,将去往80/443端口的流量导向Class 1:2,其余流量导向Class 1:3。
5.2 实战:使用HTB限制服务器出站流量
假设我们有一台服务器,eth0出口总带宽100Mbps。我们要保证:
- SSH管理流量(端口22)至少有2Mbps,最高不超过5Mbps。
- Nginx Web服务流量(端口80/443)至少有30Mbps,最高可以借用空闲带宽到80Mbps。
- 其他流量共享剩余带宽。
步骤1:清空现有规则(谨慎操作)
tc qdisc del dev eth0 root 2>/dev/null # 忽略可能的“No such file or directory”错误步骤2:创建根HTB QDisc,并设置总带宽
tc qdisc add dev eth0 root handle 1: htb default 30handle 1::给这个根QDisc一个标识符1:。htb:使用HTB算法。default 30:未分类的流量默认发送到1:30这个类。
步骤3:创建HTB根类(总带宽限制)
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbitparent 1::父节点是根QDisc1:。classid 1:1:创建类ID为1:1。rate 100mbit:保证带宽100Mbps(等于物理带宽)。ceil 100mbit:最大带宽100Mbps。
步骤4:创建子类(为不同流量分配带宽)
# 为SSH流量创建类 1:10 tc class add dev eth0 parent 1:1 classid 1:10 htb rate 2mbit ceil 5mbit burst 15k cburst 15k # 为Web流量创建类 1:20 tc class add dev eth0 parent 1:1 classid 1:20 htb rate 30mbit ceil 80mbit burst 15k cburst 15k # 为默认流量创建类 1:30 tc class add dev eth0 parent 1:1 classid 1:30 htb rate 10mbit ceil 100mbit burst 15k cburst 15kburst和cburst:令牌桶的突发尺寸,允许短时间内超过rate。通常设置为rate/8左右,但不宜过大。
步骤5:为每个类挂载叶子QDisc(进行内部排队)
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10 tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10 tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10sfq:随机公平队列,防止同一个类下的单个TCP流独占带宽。perturb 10:每10秒重置一次哈希算法,增强公平性。
步骤6:创建过滤器,将流量分类到对应的类
# 将SSH流量(目标端口22)定向到类 1:10 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:10 # 将HTTP/HTTPS流量(目标端口80/443)定向到类 1:20 tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 80 0xffff flowid 1:20 tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 443 0xffff flowid 1:20prio:过滤器优先级,数字越小优先级越高。u32:使用u32过滤器进行匹配。match ip dport 22 0xffff:匹配IP包中目标端口为22(0xffff是掩码,表示精确匹配16位端口)。flowid 1:10:匹配到的流量送往类1:10。
步骤7:验证配置
tc -s qdisc show dev eth0 # 查看队列统计 tc -s class show dev eth0 # 查看类统计 tc filter show dev eth0 # 查看过滤器5.3 高级技巧:结合iptables的MARK与tc的fw过滤器
u32过滤器语法复杂,对于更复杂的匹配(如连接状态、字符串匹配),我们可以借助iptables的MARK功能来打标,然后tc用fw过滤器根据标记来分类。这更灵活。
# 1. 用iptables给流量打标记 iptables -t mangle -A OUTPUT -p tcp --dport 22 -j MARK --set-mark 10 iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 20 iptables -t mangle -A OUTPUT -p tcp --dport 443 -j MARK --set-mark 20 # 2. 在tc中使用fw过滤器,根据mark值分类 tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 10 fw flowid 1:10 tc filter add dev eth0 protocol ip parent 1:0 prio 2 handle 20 fw flowid 1:20handle 10 fw表示匹配iptables设置的mark值为10的数据包。
踩坑实录:tc配置的持久化是个大问题。通过命令行配置的规则重启后会消失。在生产环境,你需要将tc和iptables命令写入启动脚本(如/etc/rc.local),或者使用像systemd服务单元、ifup脚本、或者专门的配置管理工具(如ansible)来管理。另外,tc命令的参数顺序非常严格,写错一个地方可能整个规则都不生效,而且错误提示往往不清晰,排错时最好从最简单的规则开始,逐步叠加测试。
6. 端到端QoS实战:从桌面到服务器的完整流量管控
理解了各个组件,我们来看一个从源到目的地的完整QoS案例:保障一个公司分支机构的语音通话质量。
场景:分支机构通过一条10Mbps的MPLS专线连接到总部。分支内部有IP电话和办公电脑。
设计目标:优先保障语音流量(G.711编码,约80Kbps一路),其次保障视频会议流量,最后是办公数据。
端到端配置思路:
接入层(交换机):
- 连接IP电话的端口配置
trust cos或trust dscp,信任电话标记的优先级。 - 连接PC的端口一般不信任,或者根据MAC地址/协议识别出软电话流量并标记。
- 在交换机上配置基于端口的优先级映射,例如将语音VLAN的流量映射到高优先级队列。
- 连接IP电话的端口配置
汇聚/核心层(三层交换机):
- 启用全局QoS。
- 配置DSCP信任(
trust dscp)。 - 配置CBWFQ出方向队列。为EF(语音)流量创建LLQ,保证带宽1Mbps(足够10路以上通话);为AF41(视频)流量分配保证带宽3Mbps;其余为默认队列。
- 在连接出口路由器的接口上应用出方向策略。
出口路由器:
- 识别流量:使用NBAR或ACL识别语音(RTP端口范围)、视频会议(如H.323, SIP, WebRTC)。
- 标记流量:如果下游设备信任DSCP,则标记语音为EF(46),视频为AF41(34)。如果与运营商采用VLAN优先级映射,则可能需要将DSCP映射回VLAN PCP(例如,EF 46映射到PCP 5或6)。
- 流量整形:将出方向流量整形为9.5Mbps(为控制协议留出空间)。
- 队列调度:应用与核心层类似的CBWFQ+LLQ策略。
- 拥塞避免:对AF类流量启用WRED。
服务器端(Linux,运行语音/视频服务器):
- 使用
tc的HTB,为语音服务端口(如UDP 10000-20000)创建高优先级类(rate和ceil设置为略高于预估总语音流量),并可能使用pfifo或fq_codel作为叶子QDisc以降低延迟。 - 为视频服务端口创建第二优先级类。
- 确保服务器的网卡驱动和内核支持并启用了
ethtool的诸如txqueuelen调整、GRO/GSO等优化。
- 使用
验证与监控:
show policy-map interface(思科)或display qos policy interface(华为):查看接口上应用的QoS策略统计信息,包括分类匹配的包数、字节数,以及队列的丢弃情况。ping与traceroute:结合DSCP标记(如ping -Q 46 <目标>)测试不同优先级流量的延迟和抖动。- Wireshark抓包:直接查看数据包中的VLAN PCP和IP DSCP字段是否按预期标记。
- 网络性能测试工具:如
iperf3,可以指定DSCP值(-S)生成测试流,验证带宽分配和优先级效果。
个人体会:部署QoS不是一劳永逸的。它需要前期的仔细规划(流量识别、分类、带宽分配)、实施时的精细配置(信任边界、标记点、策略应用方向),以及后期的持续监控和调整。最忌讳的就是在网络上到处开启QoS却不清楚流量模型,那样反而可能引入不必要的复杂性和性能开销。最好的方法是先监控一段时间,了解流量基线,然后从最关键的业务(通常是语音)开始,小范围实施,验证效果后再逐步推广。记住,QoS的目标不是让慢的变快,而是在拥塞时让重要的业务不受影响。如果链路永远不拥塞,QoS就英雄无用武之地。因此,增加带宽永远是解决拥塞问题的首选方案,QoS是在带宽不足或成本受限时的优化手段。