tc - 流量控制 (2/2)

2019 12 29, Sun

上次我们说到了一些classless qdisc,一般单独用或者作为其他classfull qdisc的leaf qdisc。

这次我们就要大概描述下classful qdisc

概念

class & filter

classful和classless听起来很像我们常说的stateful stateless,有/无状态的,classful/classless qdisc就是带分类的队列策略,而具体的分类方法就是filter。

遇到classful qdisc时,每个包到达设备上的root qdisc后,总是先通过filter一层层分类,分类为具体的class后,最后推入具体的leaf qdisc。而出队时,内核总是和root qdisc取数据包,然后root qdisc通过递归遍历子qdisc来决定具体送出哪一个数据包。

we define some classfull qdisc on a device and it could look like this:

(every x: is a qdisc, and x:y is a class)

qdisc                  class qdisc               class
  ^                      ^     ^                   ^
                                               /- 10:0
                      - 1:1 - 10: -> (filter) --- 10:1
                     /                         \- 10:2
root 1: -> (filter) --- 1:2
                     \
                      - 1:3

handle

上面的结构里面,有两种节点,一种是x:,一种是x:y。

x:y是具体的qdisc的class,x:是qdisc的handle,用来说明class和qdisc之间的从属关系。

常用的qdisc

这两天刚刚好用到了一些,比如说prio

prio

Background

有的时候机器进出的流量很大,会造成有一些对时间敏感的服务超时。

比如说我们在两个物理机之间跑iperf3:

# one host
$ iperf3 -s
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
......

# another host
$ iperf3 -c 192.168.2.181 -t 10000 -u -M 30 -b 0 -P 100 
......
[187]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[189]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[191]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[193]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[195]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[197]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[199]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[201]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[203]   3.00-4.00   sec  7.65 MBytes  64.2 Mbits/sec  5540  
[SUM]   3.00-4.00   sec   765 MBytes  6.42 Gbits/sec  554000  
......

这个时候运行访问redis,redis总是会超时的:

$  while true; do echo start $(date); redis-cli -h 192.168.2.181 keys '*'; echo done $(date); sleep 1; done
......
start 2019年 12月 25日 星期三 20:47:39 CST
Could not connect to Redis at 192.168.2.181:6379: Connection timed out
done 2019年 12月 25日 星期三 20:47:43 CST
......

prio

prio是一个比较简单的qdisc,把流量分为bands(默认为3)个优先级,对应bands个class,每个class是一个pfifo_fast,同时配置priomap。你要是自己设置bands的数量,就要自己配置priomap。不过一般默认的就够了。

逻辑基本上和pfifo_fast一样,只不过可以通过class指定某些数据包的优先级。

solution

# create a prio qdisc with handle 1:
$ sudo tc qdisc add dev enp6s0 root handle 1: prio

# classfy packets to 6379 to class 1:1(highest)
$ sudo tc filter add dev enp6s0 protocol ip parent 1: prio 1 u32 match ip dport 6379 0xffff flowid 1:1

# classfy packets to 5201 to class 1:2(higher)
$ sudo tc filter add dev enp6s0 protocol ip parent 1: prio 1 u32 match ip dport 5201 0xffff flowid 1:2

然后我们再尝试redis,就很正常了

$ while true; do echo start $(date); redis-cli -h 192.168.2.181 keys '*'; echo done $(date); sleep 1; done                                                            :(
start 2019年 12月 25日 星期三 20:57:02 CST
1) "abc"
done 2019年 12月 25日 星期三 20:57:02 CST
start 2019年 12月 25日 星期三 20:57:03 CST
1) "abc"
done 2019年 12月 25日 星期三 20:57:03 CST
start 2019年 12月 25日 星期三 20:57:04 CST
1) "abc"
done 2019年 12月 25日 星期三 20:57:04 CST

HTB

HTB逻辑上基于TBF。但是HTB允许你创建一个属性的结构,每一个节点是一个class,每个class是一个TBF。每一个节点可以限制速率、最大速率和权。

和之前博客里面提到的 TBF 不大一样的是。每个leaf在耗尽token之后,还会从他的父亲节点借token。这样就可以做到各个class都高带宽时按照各自的限制限速,只有个别class满负载的情况下占用从子节点到根节点的全部带宽。

配置略微有点复杂,但是也没复杂到哪里去。但是我觉得Debian上讲的更清楚:

Debian Wiki的TrafficControl页 Wayback machine的归档

其他qdisc

还要很多其他classful qdisc,比如说mq、cbq之类的,都和上面两个差不多。