著名的CBQ队列规定
如前所述,CBQ是最复杂,最琐碎,最难以理解,最刁钻的队列规定.这并不是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与Linux的内在机制不协调造成的.
除 了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好.它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就 应该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置时间.但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自 硬件层的两个传输请求之间的毫秒数——来代替它.这个参数可以近似地表征这个链路的繁忙程度.这样做相当慎重,而且不一定能够得到正确的结论.比如,由于 驱动程序方面或者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?由于总线设计的原因,PCMCIA网卡永远也不会达到 100Mbps.那么我们该怎么计算闲置时间呢?
如果我们引入非物理网卡——像PPPoE,PPTP——情况会变得更糟糕.因为相
当一部分有效带宽耗费在了链路维护上.那些实现了测量的人们都发现CBQ总不是非常精确甚至完全失去了其本来意义.但是,在很多场合下它还是能够很好地工作.根据下面的文档,你应该能够较好地配置CBQ来解决答多数问题.
9.5.4.1. CBQ整形的细节
如 前所述,CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实际带宽的目的.为此,它要计算两个数据包的平均发送间隔.操作期间,有效闲置时间 的测量使用EWMA(exponential weighted moving average,指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按
指数增加.UNIX的平均负载也是这样算出来的.计算出来的平均时间值减去EWMA测量值,得出的结果叫做"avgidle".最佳的链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来.
在一个过载的链路上,avgidle值应当是负的.如果这个负值太严重,CBQ就会暂时禁止发包,称为"overlimit"(越限).相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成
链路允许非常大的带宽通过.为了避免这种局面,我们用maxidle来限制avgidle的值不能太大.
理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包.但是最好参照一下下面的minburst参数.下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,以字节计.计算maxidle时需要,maxidle从maxburst得出.
bandwidth
网卡的物理带宽,用来计算闲置时间.
cell
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的.一个800字节的包和一个806字节的包可以认为耗费相同的时间.也就是说它设置时间粒度.通常设置为8,必须是2的整数次幂.
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
期望中的传输速率.也就是"油门"!在CBQ的内部由很多的微调参数.比如,那些已知队列中没有数据的类就不参加计算,越限的类将被惩罚性地降低优先级等等.都非常巧妙和复杂.
9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把
各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理.每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted roundrobin,加权轮转)过程,从优先权值小的类开始.
那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类.下面是控制WRR过程的一些参数:
allot
当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
照"weight"参数.
prio
CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就必须先服务,其他类要延后处理.
weight
"weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据.
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
次传输多少数据.请注意,在一个CBQ内部所有的类都必须使用一致的主号码!
9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽.
Isolated/sharing
凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选项.选项"sharing"是"isolated"的反义选项.
bounded/borrow
一个类也可以用"bounded"选项配置,意味着它不会向其兄弟类借用带宽.选项"borrow"是"bounded"的反义选项.
一个典型的情况就是你的一个链路上有多个客户都设置成了"isolated"和"bounded",那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽.在这样的一个类的内部的子类之间是可以互相借用带宽的.
9.5.4.4. 配置范例
这个配置把WEB服务器的流量控制为5Mbps,SMTP流量控制在3Mbps上.而且二者一共不得超过6Mbps,互相之间允许借用带宽.我们的网卡是100Mbps的.
# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit /
avpkt 1000 cell 8
# tc class add dev eth0 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.
如前所述,CBQ需要调整很多的参数.其实所有的参数上面都解释过了.相应
的HTB配置则要简明得多.
# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit /
rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 /
avpkt 1000
# tc class add dev eth0 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个类.注意我们如何根据带宽来调整weight参数的.两个类都没有配置成"bounded",但它们都连接到了类1:1上,而1:1设置了 "bounded".所以两个类的总带宽不会超过6Mbps.别忘了,同一个CBQ下面的子类的主号码都必须与CBQ自己的号码相一致!
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个FIFO队列规定.但是我们把它换成SFQ队列,以保证每个数据流都公平对待.
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
sport 80 0xffff flowid 1:3
# tc filter add dev eth0 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,那么这6M带宽将按照两个类的weight参数的比例情况进行分割:WEB服务器得到5/8的带宽, SMTP得到3/8的带宽.从这个例子来说,你也可以这么认为:WEB数据流总是会得到5/8*6Mbps=3.75Mbps的带宽.
9.5.4.5. 其它CBQ参数:split和defmap
如 前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个类去排队.除了调用过滤器,CBQ还提供了其他方式,defmap和split. 很难掌握,但好在无关大局.但是现在是解释defmap和split的最佳时机,我会尽力解释.因为你经常是仅仅需要根据TOS来进行分类,所以提供了一 种特殊的语法.当
CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为"split节点".如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,权值可以来自TOS字段或者应用程序设置的套接字选项.
数据包的优先权位与defmap字段的值进行"或"运算来决定是否存在这样的匹配.换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的方法.如果defmap等于0xff,就会匹配所有包,0则是不匹配.这个简单的配置可以帮助理解:
# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 /
cell 8 avpkt 1000 mpu 64
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit /
rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 /
avpkt 1000
一个标准的CBQ前导.
Defmap参照TC_PRIO位(我从来不直接使用数字!):
TC_PRIO.. Num 对应 TOS
-------------------------------------------------
BESTEFFORT 0 最高可靠性
FILLER 1 最低成本
BULK 2 最大吞吐量(0x8)
INTERACTIVE_BULK 4
INTERACTIVE 6 最小延迟(0x10)
CONTROL 7
TC_PRIO..的数值对应它右面的bit.关于TOS位如何换算成优先权值的细节可以参照pfifo_fast有关章节.然后是交互和大吞吐量的类:
# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit /
rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 /
avpkt 1000 split 1:0 defmap c0
# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit /
rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 /
avpkt 1000 split 1:0 defmap 3f
53
"split 队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7 和第6位,也就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.节点1:0现在应该有了这样一个表格:
priority send to
0 1:3
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加besteffort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01现在,1:0上的优先权分布应该是:
priority send to
0 1:2
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
求助: 尚未测试过"tc class change",资料上这么写的.
9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera ()正确地意识到CBQ太复杂,而且并没有按照多数常见情况进行优化.他的Hierarchical能够很好地满足这样一种情况:你有一个固定速率的链 路,希望分割给多种不同的用途使用.为每种用途做出带宽承诺并实现定量的带宽借用.HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形.它是一个分 类的令牌桶过滤器.它只有很少的参数,并且在它的网站能够找到很好的文档.随着你的HTB配置越来越复杂,你的配置工作也会变得复杂.但是使用CBQ的 话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参阅它的网站)已经成了官方内核的一部分(2.4.20-pre1,2.5.31及其后).然而,你
可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3.如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB.
9.5.5.1. 配置范例
环境与要求与上述CBQ的例子一样.
# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
作者建议2在那些类的下方放置SFQ:
# 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 10
添加过滤器,直接把流量导向相应的类:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20
这 就完了——没有没见过的或者没解释过的数字,没有不明意义的参数.HTB完成得相当不错——如果10:和20:都得到了保证的速率,剩下的就是分割了,它 们借用的比率是5:3,正如你其网的那样.未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带宽.因为我们内部使用了SFQ,而 可以公平发包.
9.6. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的"分类器链" 进行选择.这个链中包含了这个分类队列规定所需的所有过滤器.重复前面那棵树:
根 1:
|
_1:1_
/ | /
/ | /
/ | /
10: 11: 12:
/ / / /
10:1 10:2 12:1 12:2
当 一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步.典型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把 包交给12:2.你可以把后一个过滤器同时放在1:1处,可因为…having more specific tests lower in the chain.…而得到效率的提高.
另外,你不能用过滤器把数据包向"上"送.而且,使用HTB的时候应该把所
有的规则放到根上!再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所在的位置来.他们不会落到树的最底层后送到网卡!
9.6.1. 过滤器的一些简单范例
就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事情.下面我们就开始,从简单地匹配一些比较明显的特征开始.比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip dport 22 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2
什么意思呢?是说:
向eth0 上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精确匹配)的IP数据包,发送到频道10:1.向eth0上的10:节点添加一 个u32过滤规则,它的优先权是1:凡是来自80口(精确匹配)的IP数据包,发送到频道10:1.向eth0上的10:节点添加一个过滤规则,它的优先 权是2:凡是上面未匹配的IP数据包,发送到频道10:2.别忘了添加"dev eth0"(你的网卡或许叫别的名字),因为每个网卡的句柄都有完全相同的命名空间.想通过IP地址进行筛选的话,这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip dst 4.3.2.1/32 flowid 10:1
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip src 1.2.3.4/32 flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 /
flowid 10:2
这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的则送到次高权限的队列.
你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip
sport 80 0xffff flowid 10:1
9.6.2. 常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ……
这 些是所谓的"u32"匹配,可以匹配数据包的任意部分.根据源/目的地址源地址段 'match ip src 1.2.3.0/24'目的地址段 'match ip dst 4.3.2.0/24'单个IP地址使用"/32"作为掩码即可.根据源/目的端口,所有IP协议
源 'match ip sport 80 0xffff'
目的 'match ip dport 80 0xffff'
根据IP协议 (tcp, udp, icmp, gre, ipsec)
使用/etc/protocols所指定的数字.
比如: icmp是1:'match ip protocol 1 0xff'.
根据fwmark
你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数据包做整形,这就很有用了.语法是这样的:
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1
注意,这不是一个u32匹配!你可以象这样给数据包打标记:
#
iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
数字6是可以任意指定的.如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按
fwmark匹配就行了.按TOS字段 选择交互和最小延迟的数据流:
#
tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 /
m
atch ip tos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用"0x08 0xff".关于更多的过滤命令,请参照"高级过滤"那一章.
9.7. IMQ(Intermediate queueing device,中介队列设备)
中 介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的.就Linux而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队 列规定.根据这个概念,出现了两个局限:1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定
的可能性非常小).
2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速.
IMQ 就是用来解决上述两个局限的.简单地说,你可以往一个队列规定中放任何东西.被打了特定标记的数据包在netfilter的 NF_IP_PRE_ROUTING 和NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该队列规定附加到一个IMQ设备上.对数据包打标记要用到 iptables的一种处理方
法.这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当成一个个的类来看待而进行全局整形设置.你还可以做很多事情,比如:把http流量放到一个队列规定中去,把新的连接请求放到一个队列规定中去,……
9.7.1. 配置范例
我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽 .就象配置其它网卡一样:
tc qdisc add dev imq0 root handle 1: htb default 20
tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
tc qdisc add dev imq0 parent 1:20 handle 20: sfq
tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match /
ip dst 10.0.0.230/32 flowid 1:10
在这个例子中,使用了u32进行分类.其它的分类器应该也能实现.然后,被打上标记的包被送到imq0排队.
iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
ip link set imq0 up
iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle表中.语法是:
IMQ [ --todev n ]
n: imq设备的编号注:ip6tables也提供了这种处理方法.请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队.数据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的.下面是
netfilter(也就是iptables)在内核中预先定义优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
对 于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是说数据包在经过了PREROUTING链的mangle表之后才 进入imq设备.对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理本应该被filter表丢弃的数据包.
关于补丁和更多的文档请参阅imq网站.
第10章 多网卡的负载均衡
有多种手段实现这个功能.最简单,最直接的方法之一就是"TEQL"——真(或"普通的")链路均衡.就象用队列实现的大多数事情一样,负载均衡也需要双向实现.链路的两端都要参与,才有完整的效果.想象下列情况:
+-------+ eth1 +-------+
| |==========| |
"网络1" ------| A | | B |---- '网络 2'
| |==========| |
+-------+ eth2 +-------+
A 和B是路由器,我们当然假定它们全是Linux机器.如果从网络1发往网络2的流量需要A路由器同时使用两条链路发给B路由器.B路由器需要进行配置以便 适应这种情况.反向传输时也一样,当数据包从网络2发往网络1时,B路由器同时使用eth1和eth2.分配的功能是用"TEQL"设备实现的,象这样 (没有比这更简单的了):
# tc qdisc add dev eth1 root teql0
# tc qdisc add dev eth2 root teql0
# ip link set dev teql0 up
别忘了"ip link set up"命令!
这 在两台机器上都要做.teql0设备基本上是在eth1和eth2之间进行轮转发帧.用源也不会有数据从teql设备上进来,只是出现在原来的eth1和 eth2上.我们现在有了网络设备,还需要有合适的路由.方法之一就是给两个链路分配一个/31的网络,teql0也一样:在A路由器上:
# ip addr add dev eth1 10.0.0.0/31
# ip addr add dev eth2 10.0.0.2/31
# ip addr add dev teql0 10.0.0.4/31在B路由器上:
# ip addr add dev eth1 10.0.0.1/31
# ip addr add dev eth2 10.0.0.3/31
# ip addr add dev teql0 10.0.0.5/31
A路由器现在应该能够通过2个真实链路和一个均衡网卡ping通10.0.0.1,
10.0.0.3 和10.0.0.5.B路由器应该能够ping通10.0.0.0,10.0.0.2和10.0.0.4.如果成功的话,A路由器应该把10.0.0.5 作为到达网络2的路由,B路由器应该把10.0.0.4作为去往网络1的路由.在网络1是你家里的网络,而网络2是Internet这种特定场合下,A路 由器的缺省网关应该设为10.0.0.5.
/proc/sys/net/ipv4/conf/eth2/rp_filter
包的乱序也是 一个大问题.比如,有6个数据包需要从A发到B,eth1可能分到第1,3,5个包,而eth2分到第2,4,6个.在理想情况下,B路由器会按顺序收到 第1,2,3,4,5,6号包.但实际上B路由器的内核很可能按照类似2,1,4,3,6,5这样的随机顺序收到包.这个问题会把TCP/IP搞糊涂.虽 然在链路上承载不同的TCP/IP会话并没有问题,但你无法通过捆绑多个链路来增加一个ftp文件的下载速度,除非两端的操作系统都是Linux,因为 Linux的TCP/IP协议栈不那么容易被这种简单的乱序问题所蒙蔽.当然,对于大多数应用系统来说,链路的负载均衡是一个好主意.
如前所述,CBQ是最复杂,最琐碎,最难以理解,最刁钻的队列规定.这并不是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与Linux的内在机制不协调造成的.
除 了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好.它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就 应该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置时间.但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自 硬件层的两个传输请求之间的毫秒数——来代替它.这个参数可以近似地表征这个链路的繁忙程度.这样做相当慎重,而且不一定能够得到正确的结论.比如,由于 驱动程序方面或者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?由于总线设计的原因,PCMCIA网卡永远也不会达到 100Mbps.那么我们该怎么计算闲置时间呢?
如果我们引入非物理网卡——像PPPoE,PPTP——情况会变得更糟糕.因为相
当一部分有效带宽耗费在了链路维护上.那些实现了测量的人们都发现CBQ总不是非常精确甚至完全失去了其本来意义.但是,在很多场合下它还是能够很好地工作.根据下面的文档,你应该能够较好地配置CBQ来解决答多数问题.
9.5.4.1. CBQ整形的细节
如 前所述,CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实际带宽的目的.为此,它要计算两个数据包的平均发送间隔.操作期间,有效闲置时间 的测量使用EWMA(exponential weighted moving average,指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按
指数增加.UNIX的平均负载也是这样算出来的.计算出来的平均时间值减去EWMA测量值,得出的结果叫做"avgidle".最佳的链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来.
在一个过载的链路上,avgidle值应当是负的.如果这个负值太严重,CBQ就会暂时禁止发包,称为"overlimit"(越限).相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成
链路允许非常大的带宽通过.为了避免这种局面,我们用maxidle来限制avgidle的值不能太大.
理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包.但是最好参照一下下面的minburst参数.下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,以字节计.计算maxidle时需要,maxidle从maxburst得出.
bandwidth
网卡的物理带宽,用来计算闲置时间.
cell
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的.一个800字节的包和一个806字节的包可以认为耗费相同的时间.也就是说它设置时间粒度.通常设置为8,必须是2的整数次幂.
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
期望中的传输速率.也就是"油门"!在CBQ的内部由很多的微调参数.比如,那些已知队列中没有数据的类就不参加计算,越限的类将被惩罚性地降低优先级等等.都非常巧妙和复杂.
9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把
各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理.每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted roundrobin,加权轮转)过程,从优先权值小的类开始.
那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类.下面是控制WRR过程的一些参数:
allot
当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
照"weight"参数.
prio
CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就必须先服务,其他类要延后处理.
weight
"weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据.
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
次传输多少数据.请注意,在一个CBQ内部所有的类都必须使用一致的主号码!
9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽.
Isolated/sharing
凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选项.选项"sharing"是"isolated"的反义选项.
bounded/borrow
一个类也可以用"bounded"选项配置,意味着它不会向其兄弟类借用带宽.选项"borrow"是"bounded"的反义选项.
一个典型的情况就是你的一个链路上有多个客户都设置成了"isolated"和"bounded",那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽.在这样的一个类的内部的子类之间是可以互相借用带宽的.
9.5.4.4. 配置范例
这个配置把WEB服务器的流量控制为5Mbps,SMTP流量控制在3Mbps上.而且二者一共不得超过6Mbps,互相之间允许借用带宽.我们的网卡是100Mbps的.
# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit /
avpkt 1000 cell 8
# tc class add dev eth0 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.
如前所述,CBQ需要调整很多的参数.其实所有的参数上面都解释过了.相应
的HTB配置则要简明得多.
# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit /
rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 /
avpkt 1000
# tc class add dev eth0 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个类.注意我们如何根据带宽来调整weight参数的.两个类都没有配置成"bounded",但它们都连接到了类1:1上,而1:1设置了 "bounded".所以两个类的总带宽不会超过6Mbps.别忘了,同一个CBQ下面的子类的主号码都必须与CBQ自己的号码相一致!
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个FIFO队列规定.但是我们把它换成SFQ队列,以保证每个数据流都公平对待.
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
sport 80 0xffff flowid 1:3
# tc filter add dev eth0 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,那么这6M带宽将按照两个类的weight参数的比例情况进行分割:WEB服务器得到5/8的带宽, SMTP得到3/8的带宽.从这个例子来说,你也可以这么认为:WEB数据流总是会得到5/8*6Mbps=3.75Mbps的带宽.
9.5.4.5. 其它CBQ参数:split和defmap
如 前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个类去排队.除了调用过滤器,CBQ还提供了其他方式,defmap和split. 很难掌握,但好在无关大局.但是现在是解释defmap和split的最佳时机,我会尽力解释.因为你经常是仅仅需要根据TOS来进行分类,所以提供了一 种特殊的语法.当
CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为"split节点".如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,权值可以来自TOS字段或者应用程序设置的套接字选项.
数据包的优先权位与defmap字段的值进行"或"运算来决定是否存在这样的匹配.换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的方法.如果defmap等于0xff,就会匹配所有包,0则是不匹配.这个简单的配置可以帮助理解:
# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 /
cell 8 avpkt 1000 mpu 64
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit /
rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 /
avpkt 1000
一个标准的CBQ前导.
Defmap参照TC_PRIO位(我从来不直接使用数字!):
TC_PRIO.. Num 对应 TOS
-------------------------------------------------
BESTEFFORT 0 最高可靠性
FILLER 1 最低成本
BULK 2 最大吞吐量(0x8)
INTERACTIVE_BULK 4
INTERACTIVE 6 最小延迟(0x10)
CONTROL 7
TC_PRIO..的数值对应它右面的bit.关于TOS位如何换算成优先权值的细节可以参照pfifo_fast有关章节.然后是交互和大吞吐量的类:
# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit /
rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 /
avpkt 1000 split 1:0 defmap c0
# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit /
rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 /
avpkt 1000 split 1:0 defmap 3f
53
"split 队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7 和第6位,也就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.节点1:0现在应该有了这样一个表格:
priority send to
0 1:3
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加besteffort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01现在,1:0上的优先权分布应该是:
priority send to
0 1:2
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
求助: 尚未测试过"tc class change",资料上这么写的.
9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera ()正确地意识到CBQ太复杂,而且并没有按照多数常见情况进行优化.他的Hierarchical能够很好地满足这样一种情况:你有一个固定速率的链 路,希望分割给多种不同的用途使用.为每种用途做出带宽承诺并实现定量的带宽借用.HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形.它是一个分 类的令牌桶过滤器.它只有很少的参数,并且在它的网站能够找到很好的文档.随着你的HTB配置越来越复杂,你的配置工作也会变得复杂.但是使用CBQ的 话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参阅它的网站)已经成了官方内核的一部分(2.4.20-pre1,2.5.31及其后).然而,你
可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3.如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB.
9.5.5.1. 配置范例
环境与要求与上述CBQ的例子一样.
# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
作者建议2在那些类的下方放置SFQ:
# 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 10
添加过滤器,直接把流量导向相应的类:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20
这 就完了——没有没见过的或者没解释过的数字,没有不明意义的参数.HTB完成得相当不错——如果10:和20:都得到了保证的速率,剩下的就是分割了,它 们借用的比率是5:3,正如你其网的那样.未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带宽.因为我们内部使用了SFQ,而 可以公平发包.
9.6. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的"分类器链" 进行选择.这个链中包含了这个分类队列规定所需的所有过滤器.重复前面那棵树:
根 1:
|
_1:1_
/ | /
/ | /
/ | /
10: 11: 12:
/ / / /
10:1 10:2 12:1 12:2
当 一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步.典型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把 包交给12:2.你可以把后一个过滤器同时放在1:1处,可因为…having more specific tests lower in the chain.…而得到效率的提高.
另外,你不能用过滤器把数据包向"上"送.而且,使用HTB的时候应该把所
有的规则放到根上!再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所在的位置来.他们不会落到树的最底层后送到网卡!
9.6.1. 过滤器的一些简单范例
就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事情.下面我们就开始,从简单地匹配一些比较明显的特征开始.比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip dport 22 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2
什么意思呢?是说:
向eth0 上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精确匹配)的IP数据包,发送到频道10:1.向eth0上的10:节点添加一 个u32过滤规则,它的优先权是1:凡是来自80口(精确匹配)的IP数据包,发送到频道10:1.向eth0上的10:节点添加一个过滤规则,它的优先 权是2:凡是上面未匹配的IP数据包,发送到频道10:2.别忘了添加"dev eth0"(你的网卡或许叫别的名字),因为每个网卡的句柄都有完全相同的命名空间.想通过IP地址进行筛选的话,这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip dst 4.3.2.1/32 flowid 10:1
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
match ip src 1.2.3.4/32 flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 /
flowid 10:2
这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的则送到次高权限的队列.
你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip
sport 80 0xffff flowid 10:1
9.6.2. 常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ……
这 些是所谓的"u32"匹配,可以匹配数据包的任意部分.根据源/目的地址源地址段 'match ip src 1.2.3.0/24'目的地址段 'match ip dst 4.3.2.0/24'单个IP地址使用"/32"作为掩码即可.根据源/目的端口,所有IP协议
源 'match ip sport 80 0xffff'
目的 'match ip dport 80 0xffff'
根据IP协议 (tcp, udp, icmp, gre, ipsec)
使用/etc/protocols所指定的数字.
比如: icmp是1:'match ip protocol 1 0xff'.
根据fwmark
你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数据包做整形,这就很有用了.语法是这样的:
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1
注意,这不是一个u32匹配!你可以象这样给数据包打标记:
#
iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
数字6是可以任意指定的.如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按
fwmark匹配就行了.按TOS字段 选择交互和最小延迟的数据流:
#
tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 /
m
atch ip tos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用"0x08 0xff".关于更多的过滤命令,请参照"高级过滤"那一章.
9.7. IMQ(Intermediate queueing device,中介队列设备)
中 介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的.就Linux而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队 列规定.根据这个概念,出现了两个局限:1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定
的可能性非常小).
2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速.
IMQ 就是用来解决上述两个局限的.简单地说,你可以往一个队列规定中放任何东西.被打了特定标记的数据包在netfilter的 NF_IP_PRE_ROUTING 和NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该队列规定附加到一个IMQ设备上.对数据包打标记要用到 iptables的一种处理方
法.这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当成一个个的类来看待而进行全局整形设置.你还可以做很多事情,比如:把http流量放到一个队列规定中去,把新的连接请求放到一个队列规定中去,……
9.7.1. 配置范例
我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽 .就象配置其它网卡一样:
tc qdisc add dev imq0 root handle 1: htb default 20
tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
tc qdisc add dev imq0 parent 1:20 handle 20: sfq
tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match /
ip dst 10.0.0.230/32 flowid 1:10
在这个例子中,使用了u32进行分类.其它的分类器应该也能实现.然后,被打上标记的包被送到imq0排队.
iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
ip link set imq0 up
iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle表中.语法是:
IMQ [ --todev n ]
n: imq设备的编号注:ip6tables也提供了这种处理方法.请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队.数据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的.下面是
netfilter(也就是iptables)在内核中预先定义优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
对 于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是说数据包在经过了PREROUTING链的mangle表之后才 进入imq设备.对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理本应该被filter表丢弃的数据包.
关于补丁和更多的文档请参阅imq网站.
第10章 多网卡的负载均衡
有多种手段实现这个功能.最简单,最直接的方法之一就是"TEQL"——真(或"普通的")链路均衡.就象用队列实现的大多数事情一样,负载均衡也需要双向实现.链路的两端都要参与,才有完整的效果.想象下列情况:
+-------+ eth1 +-------+
| |==========| |
"网络1" ------| A | | B |---- '网络 2'
| |==========| |
+-------+ eth2 +-------+
A 和B是路由器,我们当然假定它们全是Linux机器.如果从网络1发往网络2的流量需要A路由器同时使用两条链路发给B路由器.B路由器需要进行配置以便 适应这种情况.反向传输时也一样,当数据包从网络2发往网络1时,B路由器同时使用eth1和eth2.分配的功能是用"TEQL"设备实现的,象这样 (没有比这更简单的了):
# tc qdisc add dev eth1 root teql0
# tc qdisc add dev eth2 root teql0
# ip link set dev teql0 up
别忘了"ip link set up"命令!
这 在两台机器上都要做.teql0设备基本上是在eth1和eth2之间进行轮转发帧.用源也不会有数据从teql设备上进来,只是出现在原来的eth1和 eth2上.我们现在有了网络设备,还需要有合适的路由.方法之一就是给两个链路分配一个/31的网络,teql0也一样:在A路由器上:
# ip addr add dev eth1 10.0.0.0/31
# ip addr add dev eth2 10.0.0.2/31
# ip addr add dev teql0 10.0.0.4/31在B路由器上:
# ip addr add dev eth1 10.0.0.1/31
# ip addr add dev eth2 10.0.0.3/31
# ip addr add dev teql0 10.0.0.5/31
A路由器现在应该能够通过2个真实链路和一个均衡网卡ping通10.0.0.1,
10.0.0.3 和10.0.0.5.B路由器应该能够ping通10.0.0.0,10.0.0.2和10.0.0.4.如果成功的话,A路由器应该把10.0.0.5 作为到达网络2的路由,B路由器应该把10.0.0.4作为去往网络1的路由.在网络1是你家里的网络,而网络2是Internet这种特定场合下,A路 由器的缺省网关应该设为10.0.0.5.
/proc/sys/net/ipv4/conf/eth2/rp_filter
包的乱序也是 一个大问题.比如,有6个数据包需要从A发到B,eth1可能分到第1,3,5个包,而eth2分到第2,4,6个.在理想情况下,B路由器会按顺序收到 第1,2,3,4,5,6号包.但实际上B路由器的内核很可能按照类似2,1,4,3,6,5这样的随机顺序收到包.这个问题会把TCP/IP搞糊涂.虽 然在链路上承载不同的TCP/IP会话并没有问题,但你无法通过捆绑多个链路来增加一个ftp文件的下载速度,除非两端的操作系统都是Linux,因为 Linux的TCP/IP协议栈不那么容易被这种简单的乱序问题所蒙蔽.当然,对于大多数应用系统来说,链路的负载均衡是一个好主意.
4443

被折叠的 条评论
为什么被折叠?



