解释队列和队列规定
利用队列,我们决定了数据被发送的方式.必须认识到,我们只能对发送数据进
行整形.
根据 Internet 的工作方式,我们无法直接控制别人向我们发送什么数据.有点象
我们家里的信报箱,你不可能控制全世界,联系每一个人,修改别人对你发送邮
件的数量.
然而,Internet 主要依靠 TCP/IP,它的一些特性很有用.因为 TCP/IP 没办法知道
两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的"慢起技
术") ,当因为网络容量不够而开始丢失数据时,再放慢速度.实际情况要比这
种方法更聪明,我们以后再讨论.
这就象当你尚未读完一半邮件时,希望别人停止给你寄信.与现实世界不同,在
Internet 上可以做到这一点.(译注:这个例子并不恰当,TCP/IP 的这种机制并不
是在网络层实现的,而是要靠传输层的 TCP 协议)
如果你有一个路由器,并且希望能够防止某些主机下载速度太快,你需要在你路
由器的内网卡——也就是向你的网内主机发送数据包的网卡——上进行流量整
形.
你还要保证你正在控制的是瓶颈环节.如果你有一个 100M 以太网卡,而你的路
由器的链路速度是 256k,你必须保证你发送的数据量没有超过路由器的处理能
力.否则,就是路由器在控制链路和对带宽进行整形,而不是你.可以说,我们
需要拥有的队列必须是一系列链路中最慢的环节.幸运的是这很容易.
36
9.2. 简单的无类队列规定
如前所述,利用队列,我们决定了数据被发送的方式.无类队列规定就是那样,
能够接受数据和重新编排,延迟或丢弃数据包.
这可以用作对于整个网卡的流量进行整形,而不细分各种情况.在我们进一步学
习分类的队列规定之前,理解这部分是必不可少的!
最广泛应用的规定是 pfifo_fast 队列规定,因为它是缺省配置.这也解释了为什
么其它那些复杂的功能为何如此健壮,因为那些都与缺省配置相似,只不过是其
他类型的队列而已.
每种队列都有它们各自的优势和弱点.
9.2.1. pfifo_fast
这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包
被特殊对待.至少不是非常特殊.这个队列有 3 个所谓的"频道".FIFO 规则应
用于每一个频道.并且:如果在 0 频道有数据包等待发送,1 频道的包就不会被
处理,1 频道和 2 频道之间的关系也是如此.
内核遵照数据包的 TOS 标记,把带有"最小延迟"标记的包放进 0 频道.
不要把这个无类的简单队列规定与分类的 PRIO 相混淆!虽然它们的行为有些类
似,但对于无类的 pfifo_fast 而言,你不能使用 tc 命令向其中添加其它的队列规
定.
9.2.1.1. 参数与使用
pfifo_fast 队列规定作为硬性的缺省设置,你不能对它进行配置.它缺省是这样
配置的:
priomap:
内核规定,根据数据包的优先权情况,对应相应的频道.这个对应是根据
数据包的 TOS 字节进行的.TOS 看上去是这样的:
0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | |
| 优先权 | TOS | MBZ |
| | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
TOS 字段的 4 个 bit 是如下定义的:
二进制 十进制 意义
-----------------------------------------
1000 8 最小延迟 (md)
0100 4 最大 throughput (mt)
0010 2 最大可靠* (mr)
37
7),它们不对应 TOS 映射,但是有其它的意图.
下表来自 RFC 1349,告诉你应用程序可能如何设置它们的 TOS:
TELNET 1000 (minimize delay)
控制 1000 (minimize delay) FTP
数据 0100 (maximize throughput)
TFTP 1000 (minimize delay)
命令阶段 1000 (minimize delay) SMTP
数据阶段 0100 (maximize throughput)
UDP 查询 1000 (minimize delay)
TCP 查询 0000
Domain Name Service
区域传输 0100 (maximize throughput)
NNTP 0001 (minimize monetary cost)
报错 0000
请求 0000 (mostly)
ICMP
响应 (mostly)
txqueuelen
38
队列的长度来自网卡的配置,你可以用 ifconfig 和 ip 命令修改.如设置队
列长度为 10,执行:ifconfig eth0 txqueuelen 10
你不能用 tc 命令设置这个!
9.2.2. 令牌桶过滤器(TBF)
令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到
来的数据包通过,但可能允许短暂突发流量朝过设定值.
TBF 很精确,对于网络和处理器的影响都很小.所以如果您想对一个网卡限速,
它应该成为您的第一选择!
TBF 的实现在于一个缓冲器(桶),不断地被一些叫做"令牌"的虚拟数据以特定
速率填充着. (token rate).桶最重要的参数就是它的大小,也就是它能够存储
令牌的数量.
每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除.这个算法关
联到两个流上——令牌流和数据流,于是我们得到 3 种情景:
数据流以等于令牌流的速率到达 TBF.这种情况下,每个到来的数据包都
能对应一个令牌,然后无延迟地通过队列.
数据流以小于令牌流的速度到达 TBF.通过队列的数据包只消耗了一部分
令牌,剩下的令牌会在桶里积累下来,直到桶被装满.剩下的令牌可以在
需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发
传输.
数据流以大于令牌流的速率到达 TBF.这意味着桶里的令牌很快就会被耗
尽.导致 TBF 中断一段时间,称为"越限".如果数据包持续到来,将发
生丢包.
最后一种情景非常重要,因为它可以用来对数据通过过滤器的速率进行整形.
令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包,但是持续越
限的话会导致传输延迟直至丢包.
请注意,实际的实现是针对数据的字节数进行的,而不是针对数据包进行的.
9.2.2.1. 参数与使用
即使如此,你还是可能需要进行修改,TBF 提供了一些可调控的参数.第一个参
数永远可用:
limit/latency
limit 确定最多有多少数据(字节数)在队列中等待可用令牌.你也可以
通过设置 latency 参数来指定这个参数,latency 参数确定了一个包在 TBF
中等待传输的最长等待时间.后者计算决定桶的大小,速率和峰值速率.
39
burst/buffer/maxburst
桶的大小,以字节计.这个参数指定了最多可以有多少个令牌能够即刻被
使用.通常,管理的带宽越大,需要的缓冲器就越大.在 Intel 体系上,
10 兆 bit/s 的速率需要至少 10k 字节的缓冲区才能达到期望的速率.
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会
导致潜在的丢包.
mpu
一个零长度的包并不是不耗费带宽.比如以太网,数据帧不会小于 64 字
节.Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗.
rate
速度操纵杆.参见上面的 limits!
如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况).If the
bucket contains tokens and is allowed to empty, by default it does so at
infinite speed.
如果不希望这样,可以调整入下参数:
peakrate
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样.
那可能并不是你希望的,特别是你有一个比较大的桶的时候.
峰值速率可以用来指定令牌以多块的速度被删除.用书面语言来说,就是:
释放一个数据包,但后等待足够的时间后再释放下一个.我们通过计算等
待时间来控制峰值速率
然而,由于 UNIX 定时器的分辨率是 10 毫秒,如果平均包长 10k bit,我
们的峰值速率被限制在了 1Mbps.
mtu/minburst
但是如果你的常规速率比较高,1Mbps 的峰值速率对我们就没有什么价
值.要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包.最
有效的办法就是:再创建一个令牌桶!
这第二个令牌桶缺省情况下为一个单个的数据包,并非一个真正的桶.
要计算峰值速率,用 mtu 乘以 100 就行了. (应该说是乘以 HZ 数,Intel
体系上是 100,Alpha 体系上是 1024)
9.2.2.2. 配置范例
这是一个非常简单而实用的例子:
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
为什么它很实用呢?如果你有一个队列较长的网络设备,比如 DSL modem 或者
cable modem 什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上
40
载数据绝对会破坏交互*.
这是因为上载数据会充满 modem 的队列,而这个队列为了改善上载数据的吞吐
量而设置的特别大.但这并不是你需要的,你可能为了提高交互性而需要一个不
太大的队列.也就是说你希望在发送数据的时候干点别的事情.
上面的一行命令并非直接影响了 modem 中的队列,而是通过控制 Linux 中的队
列而放慢了发送数据的速度.
把 220kbit 修改为你实际的上载速度再减去几个百分点.如果你的 modem 确实很
快,就把"burst"值提高一点.
9.2.3. 随机公平队列(SFQ)
SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一
个
简单实现.它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的
计算量却很少.
SFQ 的关键词是"会话"(或称作"流") ,主要针对一个 TCP 会话或者 UDP
流.流量被分成相当多数量的 FIFO 队列中,每个队列对应一个会话.数据按照
简单轮转的方式发送, 每个会话都按顺序得到发送机会.
这种方式非常公平,保证了每一个会话都不会没其它会话所淹没.SFQ 之所以被
称为"随机",是因为它并不是真的为每一个会话创建一个队列,而是使用一个
散列算法,把所有的会话映射到有限的几个队列中去.
因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的
机会,也就是共享带宽.为了不让这种效应太明显,SFQ 会频繁地改变散列算法,
以便把这种效应控制在几秒钟之内.
有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ 才
会起作用!否则在你的 Linux 机器中根本就不会有队列,SFQ 也就不会起作用.
稍后我们会描述如何把 SFQ 与其它的队列规定结合在一起,以保证两种情况下
都比较好的结果.
特别地,在你使用 DSL modem 或者 cable modem 的以太网卡上设置 SFQ 而不进
行任何进一步地流量整形是无谋的!
9.2.3.1. 参数与使用
SFQ 基本上不需要手工调整:
perturb
多少秒后重新配置一次散列算法.如果取消设置,散列算法将永远不会重
新配置(不建议这样做).10 秒应该是一个合适的值.
quantum
41
一个流至少要传输多少字节后才切换到下一个队列.却省设置为一个最大
包的长度(MTU 的大小).不要设置这个数值低于 MTU!
9.2.3.2. 配置范例
如果你有一个网卡,它的链路速度与实际可用速率一致——比如一个电话
MODEM——如下配置可以提高公平*:
# tc qdisc add dev ppp0 root sfq perturb 10
# tc -s -d qdisc ls
qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb
10sec
Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)
"800c:"这个号码是系统自动分配的一个句柄号,"limit"意思是这个队列中可
以有 128 个数据包排队等待.一共可以有 1024 个散列目标可以用于速率审计,
而其中 128 个可以同时激活.(no more packets fit in the queue!)每隔 10 秒
种散列
算法更换一次.
9.3. 关于什么时候用哪种队列的建议
总之,我们有几种简单的队列,分别使用排序,限速和丢包等手段来进行流量整
形.
下列提示可以帮你决定使用哪一种队列.涉及到了第 14 章 所描述的的一些队列
规定:
如果想单纯地降低出口速率,使用令牌桶过滤器.调整桶的配置后可用于
控制很高的带宽.
如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,
使用随机公平队列.
如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随
机丢包(参见"高级"那一章).
如果希望对入口流量进行"整形"(不是转发流量),可使用入口流量策略,
注意,这不是真正的"整形".
如果你正在转发数据包,在数据流出的网卡上应用 TBF.除非你希望让数
据包从多个网卡流出,也就是说入口网卡起决定性作用的时候,还是使用
入口策略.
如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载
而需要使用队列,使用 pfifo 队列(不是 pfifo_fast).它缺乏内部频道但是
可以统计 backlog.
最后,你可以进行所谓的"社交整形".你不能通过技术手段解决一切问
题.用户的经验技巧永远是不友善的.正确而友好的措辞可能帮助你的正
确地分配带宽!
42
9.4. 术语
为了正确地理解更多的复杂配置,有必要先解释一些概念.由于这个主题的历史
不长和其本身的复杂*,人们经常在说同一件事的时候使用各种词汇.
以下来自 draft-ietf-diffserv-model-06.txt,Diffserv 路由器的建议管理模
型. 可以
在以下地址找到:
http://www.ietf.org/internet-drafts/draft-ietf-diffserv-model-06.txt.
关于这些词语的严格定义请参考这个文档.
队列规定
管理设备输入(ingress)或输出(egress)的一个算法.
无类的队列规定
一个内部不包含可配置子类的队列规定.
分类的队列规定
一个分类的队列规定内可一包含更多的类.其中每个类又进一步地包含一
个队列规定,这个队列规定可以是分类的,也可以是无类的.根据这个定
义,严格地说 pfifo_fast 算是分类的,因为它实际上包含 3 个频道(实际上
可以认为是子类).然而从用户的角度来看它是无类的,因为其内部的子
类无法用 tc 工具进行配置.
类
一个分类的队列规定可以拥有很多类,类内包含队列规定.
分类器
每个分类的队列规定都需要决定什么样的包使用什么类进行发送.分类器
就是做这个用的.
过滤器
分类是通过过滤器完成的.一个过滤器包含若干的匹配条件,如果符合匹
配条件,就按此过滤器分类.
调度
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据
包之前发送.这种处理叫做"调度",比如此前提到的 pfifo_fast 就是这样
的.调度也可以叫做"重排序",但这样容易混乱.
整形
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速
率,这种处理叫做"整形".整形在 egress 处进行.习惯上,通过丢包来
降速也经常被称为整形.
43
| 队列规定 \__队列规定 4__/ |
| \-队列规定 N_/ |
| |
+------------------------------------------------------------+
感谢 Jamal Hadi Salim 制作的 ASCII 字符图像.
整个大方框表示内核.最左面的箭头表示从网络上进入机器的数据包.它们进入
Ingress 队列规定,并有可能被某些过滤器丢弃.即所谓策略.
这些是很早就发生的(在进入内核更深的部分之前).这样早地丢弃数据有利于
节省 CPU 时间.
数据包顺利通过的话,如果它是发往本地进程的,就会进入 IP 协议栈处理并提
交给该进程.如果它需要转发而不是进入本地进程,就会发往 egress.本地进程
也可以发送数据,交给 Egress 分类器.
然后经过审查,并放入若干队列规定中的一个进行排队.这个过程叫做"入队".
在不进行任何配置的情况下,只有一个 egress 队列规定——pfifo_fast——总
是接
收数据包.
数据包进入队列后,就等待内核处理并通过某网卡发送.这个过程叫做"出队".
44
这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况.
每块网卡都有它自己的 ingress 和 egress.
9.5. 分类的队列规定
如果你有多种数据流需要进行区别对待,分类的队列规定就非常有用了.众多分
类的队列规定中的一种——CBQ(Class Based Queueing,基于类的队列)——经常
被提起,以至于造成大家认为 CBQ 就是鉴别队列是否分类的标准,这是不对的.
CBQ 不过是家族中最大的孩子而已,同时也是最复杂的.它并不能为你做所有
的事情.对于某些人而言这有些不可思议,因为他们受"sendmail 效应"影响较
深,总是认为只要是复杂的并且没有文档的技术肯定是最好的.
9.5.1. 分类的队列规定及其类中的数据流向
一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要
分类.对数据包进行分类的工具是过滤器.一定要记住:"分类器"是从队列规
定内部调用的,而不是从别处.
过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排
队.每个子类都可以再次使用它们的过滤器进行进一步的分类.直到不需要进一
步分类时,数据包才进入该类包含的队列规定排队.
除了能够包含其它队列规定之外,绝大多数分类的队列规定黑能够流量整形.这
对于需要同时进行调度(如使用 SFQ)和流量控制的场合非常有用.如果你用一个
高速网卡(比如以太网卡)连接一个低速设备(比如 cable modem 或者 ADSL modem)
时,也可以应用.
如果你仅仅使用 SFQ,那什么用也没有.因为数据包进,出路由器时没有任何延
迟.虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度.
9.5.2. 队列规定家族:根,句柄,兄弟和父辈
每块网卡都有一个出口"根队列规定",缺省情况下是前面提到的 pfifo_fast 队
列
规定.每个队列规定都指定一个句柄,以便以后的配置语句能够引用这个队列规
定.除了出口队列规定之外,每块网卡还有一个入口,以便 policies 进入的数据
流.
队列规定的句柄有两个部分:一个主号码和一个次号码.习惯上把根队列规定称
为"1:",等价于"1:0".队列规定的次号码永远是 0.
类的主号码必须与它们父辈的主号码一致.
9.5.2.1. 如何用过滤器进行分类
下图给出一个典型的分层关系:
45
12:2
也就是说,根所附带的一个过滤器要求把数据包直接交给 12:2.
9.5.2.2. 数据包如何出队并交给硬件
当内核决定把一个数据包发给网卡的时候,根队列规定 1:会得到一个出队请求,
然后把它传给 1:1,然后依次传给 10:,11:和 12:,which each query their
siblings,
然后试图从它们中进行 dequeue()操作.也就是说,内和需要遍历整颗树,因为
只有 12:2 中才有这个数据包.
换句话说,类及其兄弟仅仅与其"父队列规定"进行交谈,而不会与网卡进行交
谈.只有根队列规定才能由内核进行出队操作!
更进一步,任何类的出队操作都不会比它们的父类更快.这恰恰是你所需要的:
我们可以把 SFQ 作为一个子类,放到一个可以进行流量整形的父类中,从而能
够同时得到 SFQ 的调度功能和其父类的流量整形功能.
9.5.3. PRIO 队列规定
PRIO 队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分.
你可以认为 PRIO 队列规定是 pfifo_fast 的一种衍生物,区别在每个频道都是一
个单独的类,而非简单的 FIFO.
当数据包进入 PRIO 队列规定后,将根据你给定的过滤器设置选择一个类.缺省
情况下有三个类,这些类仅包含纯 FIFO 队列规定而没有更多的内部结构.你可
以把它们替换成你需要的任何队列规定.
每当有一个数据包需要出队时,首先处理:1 类.只有当标号更小的类中没有需要
处理的**,才会标号大的类.
46
当你希望不仅仅依靠包的 TOS,而是想使用 tc 所提供的更强大的功能来进行数
据包的优先权划分时,可以使用这个队列规定.它也可以包含更多的队列规定,
而 pfifo_fast 却只能包含简单的 fifo 队列规定.
因为它不进行整形,所以使用时与 SFQ 有相同的考虑:要么确保这个网卡的带
宽确实已经占满,要么把它包含在一个能够整形的分类的队列规定的内部.后者
几乎涵盖了所有 cable modems 和 DSL 设备.
严格地说,PRIO 队列规定是一种 Work-Conserving 调度.
9.5.3.1. PRIO 的参数与使用
tc 识别下列参数:
bands
创建频道的数目.每个频道实际上就是一个类.如果你修改了这个数值,
你必须同时修改:
priomap
如果你不给 tc 提供任何过滤器,PRIO 队列规定将参考 TC_PRIO 的优先
级来决定如何给数据包入队.
它的行为就像前面提到过的 pfifo_fast 队列规定,关于细节参考前面章节.
频道是类,缺省情况下命名为主标号:1 到主标号:3.如果你的 PRIO 队列规定是
12:,把数据包过滤到 12:1 将得到最高优先级.
注意:0 频道的次标号是 1!1 频道的次标号是 2,以此类推.
9.5.3.2. 配置范例
我们想创建这个树:
root 1: prio