Linux内核TC工具链路带宽设计--CBQ队列规定
1.1 著名的 CBQ 队列规定
除了可以分类之外,CBQ 也是一个整形器。如果想把一个 10Mbps 的连接整形成 1Mbps 的速率,就应该让链路 90%的时间处于闲置状态,必要的话就强制,以保证 90% 的闲置时间。但闲置时间的测量非常困难,所以 CBQ 就采用了它一个近似值——来自硬件层的两个传输请求之间的毫秒数——来代替它。这个参数可以近似地表征链路的繁忙程度。
1.1.1 CBQ 整形的细节
CBQ 的工作机制是确认链路的闲置时间足够长,以达到降低链路实际带宽的目的。为此,它要计算两个数据包的平均发送间隔。有效闲置时间的测量使用EWMA(exponential weighted moving average, 指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按指数增加。UNIX 的平均负载也是这样算出来的。计算出来的平均时间值减去 EWMA 测量值,得出的结果叫做“avgidle”。最佳的链路负载情况下,这个值应当是 0,数据包严格按照计算出来的时间间隔到来。
在一个过载的链路上,avgidle 值应当是负的。如果这个负值太严重,CBQ 就会暂时禁止发包,称为“overlimit”(越限)。相反地,一个闲置的链路应该有很大的 avgidle 值,这样闲置几个小时后,会造成链路允许非常大的带宽通过。为了避免这种局面,通常用 maxidle 来限制 avgidle 的值不能太大。
理论上讲,如果发生越限,CBQ 就会禁止发包一段时间(长度就是事先计算出来的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包。
下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,以字节计。计算 maxidle 时需要,maxidle 从 maxburst 得出。
bandwidth
网卡的物理带宽,用来计算闲置时间。
cell
包间隔发送单元的大小。
maxburst
这个参数的值决定了计算 maxidle 所使用的数据包的个数。在 avgidle 跌落到 0 之前,这么多的数据包可以突发传输出去。这个值越高,越能够容纳突发传输。无法直接设置 maxidle 的值,必须通过这个参数来控制。
minburst
如前所述,发生越限时 CBQ 会禁止发包。实现这个的理想方案是根据事先计算出的闲置时间进行延迟之后,发一个数据包。然而,UNIX 的内核一般来说都有一个固定的调度周期(一般不大于10ms),所以最好禁止发包的时间稍长一些,然后突发性地传输 minburst 个数据包,而不是一个一个地传输。等待的时间叫做 offtime。从大的时间尺度上说,minburst 值越大,整形越精确。但是,从毫秒级的时间尺度上说,就会有越多的突发传输。
minidle
如果 avgidle 值降到 0,也就是发生了越限,就需要等待,直到 avgidle 的值足够大才发送数据包。为避免因关闭链路太久而引起的意外突发传输, 在 avgidle 的值太低的时候会被强制设置为 minidle 的值。参数 minidle 的值是以负微秒记的。10 代表 avgidle 被限制在-10us 上。
mpu
最小包传输包大小——因为即使是 0 长度的数据包,在以太网上也要生成封装成 64 字节的帧,而需要一定时间去传输。为了精确计算闲置时间,CBQ 需要知道这个值。
rate
实际分配的带宽。
1.1.2 CBQ 在分类方面的行为
除了使用上述 idletime 近似值进行整形之外,CBQ 还可以象 PRIO 队列那样,把各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理。每当网卡请求把数据包发送到网络上时,都会开始一个 WRR(weighted round robin,加权轮转)过程,从优先权值小的类开始。那些队列中有数据的类就会被分组并被请求出队。在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类。
下面是控制 WRR 过程的一些参数:
allot
最大传输单元加 MAC 头的大小。当从外部请求一个 CBQ 发包的时候,它就会按照“priority”参数指定的顺序轮流尝试其内部的每一个类的队列规定。当轮到一个类发数据时,它只能发送一定量的数据。“allot”参数就是这个量的基值。
prio
CBQ 可以象 PRIO 设备那样工作。其中“prio”值较低的类只要有数据就必须先服务,其他类要延后处理。
weight
“weight”参数控制 WRR 过程。每个类都轮流取得发包的机会。如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据。CBQ 会把一个类下面所有的 weight 值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以。通常把“速率/10”作为参数的值来使用。归一化值后的值乘以“allot”参数后,决定了每次传输多少数据。
需要注意的是,在一个 CBQ 内部所有的类都必须使用一致的主号码。
1.1.3 决定链路的共享和借用的 CBQ 参数
除了纯粹地对某种数据流进行限速之外,CBQ 还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽。
isolated/sharing
凡是使用“isolated”选项配置的类,就不会向其兄弟类出借带宽。选项“sharing”是“isolated”的反义选项。
bounded/borrow
一个类也可以用“bounded”选项配置,意味着它不会向其兄弟类借用带宽。选项“borrow”是“bounded”的反义选项。
一个典型的情况就是一个链路上有多个客户都设置成了“isolated”和“bounded”,那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽。在这样的一个类的内部的子类之间是可以互相借用带宽的。
1.1.4 配置范例
这个配置把 WEB 服务器的流量控制为 5Mbps、SMTP 流量控制在 3Mbps。而且二者一共不得超过 6Mbps,互相之间允许借用带宽。网卡带宽为 100Mbps。
# sudo tc qdisc add dev enp0s5 root handle 1: cbq bandwidth 100Mbit avpkt 1000 cell 8 # sudo tc class add dev enp0s5 parent 1:0 classid 1:1 cbq bandwidth 100Mbit rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 avpkt 1000 bounded
这里设置了根为 1:0,并且绑定了类 1:1。也就是说整个带宽不能超过 6Mbps。
# sudo tc class add dev enp0s5 parent 1:1 classid 1:3 cbq bandwidth 100Mbit rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000 # sudo tc class add dev enp0s5 parent 1:1 classid 1:4 cbq bandwidth 100Mbit rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000
这里建立了 2 个类。两个类都没有配置成“bounded”,但它们都连接到了类 1:1 上,而 1:1 设置了“bounded”。 所以两个类的总带宽不会超过 6Mbps。需要注意的是,同一个 CBQ 下面的子类的主号码都必须与 CBQ 自己的主号码相一致。
# sudo tc qdisc add dev enp0s5 parent 1:3 handle 30: sfq # sudo tc qdisc add dev enp0s5 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个 FIFO 队列规定。这里把它换成 SFQ 队列,以保证每个数据流都公平对待。
# sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 1 u32 match ip sport 80 0xffff flowid 1:3 # sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 1 u32 match ip sport 25 0xffff flowid 1:4
这里规定了根上的过滤器,保证数据流被送到正确的队列规定中去。注意:上面命令先使用了“tc class add”在一个队列规定中创建了类,然后使用“tc qdisc add”在类中创建队列规定。那些没有被那两条规则分类的数据流被 1:0 直接处理,没有限制。如果 SMTP+web 的总带宽需求大于 6Mbps,那么这 6Mbps 带宽将按照两个类的 weight 参数的比例情况进行分割:WEB 服务器得到 5/8 的带宽,SMTP 得到 3/8 的带宽。从这个例子来说,可以这么认为:WEB 数据流总是会得到 5/8*6Mbps=3.75Mbps 的带宽。
1.2 一个完整的 CBQ 队列规定实例
流量需求:
流量控制器上的以太网卡(enp0s5)的IP地址为 10.211.55.17, 在其上建立一个 CBQ 队列。假设包的平均大小为 1000 字节,包间隔发送单元的大小为 8 字节,可接收冲突的发送最长包的数目为 20 字节。
加入有三种类型的流量需要控制:
(1)是发往主机 1 的,其 IP 地址为1 0.211.55.20。其流量带宽控制在 8Mbit,优先级为 2;
(2)是发往主机 2 的,其 IP 地址为 10.211.55.21。其流量带宽控制在 1Mbit,优先级为 1;
(3)是发往子网 1 的,其子网号为 10.211.55.0。 子网掩码为 255.255.255.0。流量带宽控制在 1Mbit,优先级为 6。
创建 CBQ 队列主要分为四个步骤:建立队列、建立分类、创建过滤器、建立路由。在建立队列之前,需要对网卡的队列规则进行清除,具体操作如下:
清除网卡所有队列规则
# sudo tc qdisc del dev enp0s5 root 2> /dev/null > /dev/null
建立队列
# sudo tc qdisc add dev enp0s5 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64
将一个 cbq 队列绑定到物理网络设备 enp0s5 上,其编号为 1:0;物理网络设备 enp0s5 的实际带宽为 10Mbit,包的平均大小为 1000 字节;包间隔发送单元的大小为 8 字节,最小传输包大小为 64 字节。
建立分类
创建根分类 1:1,分配带宽为 10Mbit,优先级别为 8:
# sudo tc class add dev enp0s5 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate 10Mbit maxburst 20 allot 1514 prio 8 avpkt 1000 cell 8 weight 1Mbit
该队列的最大可用带宽为 10Mbit,实际分配的带宽为 10Mbit,可接收冲突的发送最长包数目为 20 字节;最大传输单元加 MAC 头的大小为 1514 字节,优先级别为 8,包的平均大小为 1000 字节,包间隔发送单元的大小为 8 字节,相当于实际带宽的加权速率为 1Mbit。
创建分类 1:2,其父类为 1:1,分配带宽为 8Mbit,优先级别为 2:
# sudo tc class add dev enp0s5 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 8Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 800Kbit split 1:0 bounded
该队列的最大可用带宽为 10Mbit,实际分配的带宽为 8Mbit,可接收冲突的发送最长包数目为 20 字节;最大传输单元加 MAC 头的大小为 1514 字节,优先级别为 2,包的平均大小为 1000 字节,包间隔发送单元的大小为 8 字节,相当于实际带宽的加权速率为 800Kbit,分类的分离点为 1:0,且不可借用未使用带宽。
创建分类 1:3,其父分类为 1:1,分配带宽为 1Mbit,优先级别为 1:
# sudo tc class add dev enp0s5 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 100Kbit split 1:0
该队列的最大可用带宽是 10Mbit,实际分配的带宽为 1Mbit,可接收冲突的发送最长包数目为 20 字节;最大传输单元加 MAC 头的大小为 1514 字节,优先级别为 1,包的平均大小为 1000 字节,包间隔发送单元的大小为 8 字节,相当于实际带宽的加权速率为 100Kbit,分类的分离点为 1:0。
创建分类 1:4,其父分类为 1:1,分配带宽为 1Mbit,优先级别为 6:
# sudo tc class add dev enp0s5 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 100Kbit split 1:0
该队列的最大可用带宽为 10Mbit,实际分配的带宽为 1Mbit,可接收冲突的发送最长包数目为 20 字节;最大传输单元加 MAC 头的大小为 1514 字节,优先级别为 6,包的平均大小为 1000 字节,包间隔发送单元的大小为 8 字节,相当于实际带宽的加权速率为 100Kbit,分类的分离点为 1:0。
创建过滤器
(1)应用路由分类器到 cbq 队列的根,父分类编号为 1:0;过滤协议为 ip,优先级别为 100,过滤器为基于路由表:
# sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 100 route
(2)建立路由映射分类 1:2 , 1:3 , 1:4
# sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 100 route to 2 flowid 1:2 # sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 100 route to 3 flowid 1:3 # sudo tc filter add dev enp0s5 parent 1:0 protocol ip prio 100 route to 4 flowid 1:4
建立路由
(1)发往主机 10.211.55.20 的数据包通过分类 2 转发(分类 2 的速率 8Mbit)
# sudo ip route add 10.211.55.20 dev enp0s5 via 10.211.55.17 realm 2
(2)发往主机 10.211.55.21 的数据包通过分类 3 转发(分类 3 的速率 1Mbit)
# sudo ip route add 10.211.55.21 dev enp0s5 via 10.211.55.17 realm 3
(3)发往子网 10.211.55.0/24 的数据包通过分类 4 转发(分类 4 的速率 1Mbit)
# sudo ip route add 10.211.55.0/24 dev enp0s5 via 10.211.55.17 realm 4