TC(Traffic Control)是Linux内核中目前用于网络带宽流控非常成熟的一个工具,通过使用TC灵活的创建各种队列、并定义队列中的数据包被发送的方式, 从而实现对流量的控制。比如开源的 mesos 使用tc来限制job流量,并在twitter公司内大规模推广
Linux高级路由控制:http://lartc.org/howto/
TC文档:http://tldp.org/HOWTO/Traffic-Control-HOWTO/index.html
1. 原理
TC中主要包含两种队列:一类是无类队列(classless qdiscs)规定, 另一类是分类队列(classful qdiscs)规定。 无类队列规定相对简单,而分类队列规定则引出了分类和过滤器等概念,使其流量控制功能增强。
无类队列规定是对进入网络设备的数据流不加区分统一对待的队列规定。使用无类队列规定形成的队列能够 接受数据包以及重新编排、延迟或丢弃数据包。这类队列规 定形成的队列可以对整个网络设备的流量进行整形, 但不能细分各种情况。
常用的无类队列规定主要有: http://tldp.org/HOWTO/Traffic-Control-HOWTO/classless-qdiscs.html
- FIFO, First-In First-Out (pfifo and bfifo)
- pfifo_fast, the default Linux qdisc
- SFQ, Stochastic Fair Queuing
- ESFQ, Extended Stochastic Fair Queuing
- TBF, Token Bucket Filter
这类队列规定使用的流量整形手段主要是:排序、 限速和丢包
分类队列规定是对进入网络设备的数据包根据不同的需求以分类的方式区分对待的队列规定。 数据包进入一个分类的队列后, 它就需要被送到某一个类中, 也就是说需要对数据包做分类处理。
对数据包进行分类的工具是过滤器Filter,过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排队。每个子类都可 以再次使用它们的过滤器进行进一步的分类。直到不需要进一步分类时, 数据包才进入该类包含的队列排队。 除了能够包含其它队列规定之外, 绝大多数分类的队列规定还能够对流量进行整形。这对于需要同时进行调度( 如使用TBF) 和流量控制的场合非常有用
分类队列主要有: http://tldp.org/HOWTO/Traffic-Control-HOWTO/classful-qdiscs.html
- HTB, Hierarchical Token Bucket
- HFSC, Hierarchical Fair Service Curve
- PRIO, priority scheduler
- CBQ, Class Based Queuing
除此之外,Class、Filter也是tc中的核心概念:http://tldp.org/HOWTO/Traffic-Control-HOWTO/components.html
2. HTB分层令牌桶
分层令牌桶是tc中最强大的工具,没有之一,htb使用令牌桶的原理,结合分类、过滤器,可以实现非常复杂的流控,更重要的是,htb支持流量租借,叶子节点流量不足时,可以租借父节点的流量,只要父节点流量还有冗余的情况下。
2.1. 令牌桶
令牌桶是HTB的基础,HTB主要是在令牌桶的原理之上支持了分层机制
令牌桶算法是网络流量整形和速率限制中最常使用的一种算法,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送
首先令牌桶有一个容量,这个容量是固定的,令牌桶可以自行以恒定的速率源源不断的产生令牌:
- 如果令牌被消耗速度跟不上产生速度,最终就会导致令牌桶中的令牌数不断增多,直至桶满,然后开始溢出,最后桶中的令牌绝对不会超出桶的容量
- 如果令牌被消耗的速度超过令牌产生的速度,那么桶中的令牌就会越来越少,最终桶就会变空
tc使用令牌来描述网络传输数据包的单位,使用令牌桶来控制数据包发送到网络上的传输速度,令牌可以用来表示网络数据包的大小,不同的数据包消耗的令牌数是不一样的
令牌桶的控制传输速率的基本原理就是:
- 数据包经过令牌桶时,如果桶中有足够的令牌,则发送数据包,同时减去响应的令牌数
- 如果桶中的令牌数不足,那么数据包就不能被发送,需要等待,直到令牌桶中有足够的令牌
2.2. 分层
分层是HTB中另一个重要的概念,这是CBQ没有的,CBQ不但复杂,而且因为和Linux内核机制不兼容导致多数情况下都无法按照预期进行优化。所以作者Martin Devera搞了一个HTB出来,HTB能够很好地满足这样一种场景:
你有一个固定速率的链路,希望分割给多种不同的用途使用,为每种用途做出带宽承诺并实现定量的带宽租借
htb可以创建分层的队列规则,不同的层级还可以继续创建子分层,从而形成一个拓扑型的流量控制,如下:
2.3. 分类
HTB中的分类其实就是分层拓扑中的节点,不同的类有不同的带宽配置,详细的配置如下:
tc class add ... htb rate R1 burst B1 [prio P] [slot S] [pslot PS]
[ceil R2] [cburst B2] [mtu MTU] [quantum Q]
rate rate allocated to this class (class can still borrow)
burst max bytes burst which can be accumulated during idle period {computed}
ceil definite upper class rate (no borrows) {rate}
cburst burst but for ceil {computed}
mtu max packet size we create rate map for {1600}
prio priority of leaf; lower are served first {0}
quantum how much bytes to serve from leaf at once {use r2q}
其中有几个比较关键的概念:
- rate:带宽速率,单位是bit/mbit/kbit/gbit或者bps/mbps/kbps/gbps
- ceil:上限带宽速率,单位与rate一致,ceil是实现HTB的流量租借的重要参数
- burst:突发数据包大小
- cburst:租借情况下的突发数据包大小
其中rate和ceil有点类似于 cgroup 中的 quota 和 limit 语义,也就是软限和硬限,burst主要用来控制流量整形的精确度
2.4. 过滤器
内核只和根打交道,数据包是在根队列规定处入队和出队的:
数据包进入队列时,自上而下进入拓扑中的某个子节点
当内核决定把一个数据包发给网卡的时候,根队列规定会得到一个出队请求,然后它会把请求以递归的方式发送给所有的孩子,最终只有叶子节点才能执行真正的数据包出队操作
为了决定用哪个类处理数据包,必须调用所谓的分类选择器进行选择,这就是tc中的filter概念
filter会根据自身设定的规则,以决定当前数据包进入哪一个孩子节点,目前tc支持的分类器有fw,u32,route,cgroup等。它们主要的区别是:
- fw 根据防火墙如何对这个数据包做标记进行判断
- u32 根据数据包中的各个字段进行判断
- route 根据数据如何被路由进行判断
- cgroup 与net_cls子系统结合,根据进程pid来判断
其中cgroup正是我们想要的
2.5. 流量租借
流量租借是htb中非常核心的功能,htb拓扑中的孩子节点带宽耗尽之后,可以向父节点租借流量,如果父节点还有冗余的话。但是租借的流量最终不能超过设定的一个上线,如前面我们再tc的htb用法中看到的ceil参数,htb中每个节点都有两个很重要的参数:
- rate 表示节点所拥有的带宽速率
- ceil 表示节点的上限带宽速率
默认情况下,如果不指定ceil,则ceil取rate指,也就是ceil == rate,意思就是说突发流量不能超过rate,从而实现了硬限的原理,详细租借规则如下:
HTB class states and potential actions taken
节点类型 | 当前带宽 | 状态 | 备注 |
---|---|---|---|
leaf | < rate |
允许发送 | Leaf class will dequeue queued bytes up to available tokens (no more than burst packets) |
leaf | > rate && < ceil |
允许租借 | Leaf class will attempt to borrow tokens/ctokens from parent class. If tokens are available, they will be lent in quantum increments and the leaf class will dequeue up to cburst bytes |
leaf | > ceil |
不允许发送 | No packets will be dequeued. This will cause packet delay and will increase latency to meet the desired rate. |
inner,root | < rate |
允许发送 | Inner class will lend tokens to children. |
inner,root | > rate && < ceil |
允许租借 | Inner class will attempt to borrow tokens/ctokens from parent class, lending them to competing children in quantum increments per request. |
inner,root | > ceil |
不允许发送 | Inner class will not attempt to borrow from its parent and will not lend tokens/ctokens to children classes. |