Linux服务器网络限流Traffic Control

cuixiaogang

项目介绍

业务通过接收URL,对URL中的资源进行下载(支持分片、代理等功能)。服务器存在多个集群,这些集群共同消费一个RabbitMQ,每个集群下载的文件速度和数量取决于当前集群下的机器状态。比如万兆网卡机器的下载速度肯定要远远高于千兆网卡机器的下载速度。同时,不同集群使用docker-swarm进行服务部署,便于管理、维护。

集群情况

  • A机房
    • 1集群:6台万兆网卡机器
    • 2集群:12台千兆网卡机器
  • B机房
    • 3集群:8台万兆网卡机器
  • C机房
    • 4集群:16台千兆网卡机器
  • D机房
    • 5集群:22台千兆网卡机器

问题说明

  • 业务日常运行时没有问题,但每天在一个固定时间下,上游推送的URL会激增,导致所有集群的上游出现网络峰值
  • A机房的带宽费用跟峰值挂钩(每个机房的签约合同不一样,带宽费用的结算方式也不一样),其他机房则没有这个问题
  • URL队列中的数据不能长期积压,否则有失效的风险(一般的URL都有时效性)

通过上面问题的汇总,需要我们单独的对 “1集群” 的6台万兆网卡机器做独立的限流,保证峰值不超过阈值,同时还不能对数据的消费造成较大的影响

方案分析

第一种方案

对业务进行改造,使用docker-swarm部署时,通过环境变量(environment)的方式,为不同的集群传入一个不同的阈值,容器内的业务在运行过程中,实时查看当前网卡的近期下载速度,然后限流

存在的问题

  1. 容器内服务直接获取到宿主机的网卡信息,可以通过以下方式解决
    • 将网卡信息/proc/net/dev通过映射(volumn)的方式挂载到容器中,然后业务可以直接读取该文件既可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 eth2: 1077943200252635 745415886069    0 366195226    0     0          0   4915158 138918837199706 253225064446    0    0    0     0       0          0

// 接收(Rx)统计
// bytes 1077943200252635 累计接收的字节数(约 1077.9 TB)
// packets 745415886069 累计接收的数据包数量(约 7454 亿个)
// errs 0 接收时产生的错误
包数量(0 表示无错误)
// drop 366195226 接收时丢弃的数据包数量(约 3.66 亿个)
// fifo 0 FIFO 缓冲区错误数量
// frame 0 帧错误数量(通常是物理层问题)
// compressed 0 接收的压缩数据包数量
// multicast 4915158 接收的多播数据包数量

// 发送(Tx)统计
// bytes 138918837199706 累计发送的字节数(约 138.9 TB)
// packets 253225064446 累计发送的数据包数量(约 2532 亿个)
// errs 0 发送时产生的错误包数量(0 表示无错误)
// drop 0 发送时丢弃的数据包数量
// fifo 0 FIFO 缓冲区错误数量
// colls 0 发送时的冲突数量(在半双工模式下可能出现)
// carrier 0 载波错误数量(通常是物理层问题)
// compressed 0 发送的压缩数据包数量
  1. 需要对不同的集群设置不同的阈值,导致每个集群的docker-compose.yaml的配置不一样,不方便管理

第二种方案

请求运维在机房端(比如交换机)增加限制,以实现限流的目的

存在的问题

  1. 机房端直接限制会增加网络丢包的风险,因为一般的交换机内存/处理器的配置都要远远低于服务器,它们限流的方式主要是监控数据包,如果数据包超过阈值,则直接丢弃
  2. 交换机直接限流,会造成后续维护的困难,有可能在业务交替、机器替换时出现问题

第三种方案

在服务器上使用Traffic Control(流量控制)来限流,这也是我们选择的方案

Traffic Control介绍及使用

Traffic Control原理

  • Traffic Control通过令牌桶算法,以固定速率生成令牌
  • 每个数据包需要消耗一定数量的令牌
  • 而当令牌桶中没有令牌时,数据包会被排队等待,当队列满(默认1MB)后才开始丢包

基于上述的概念,理论上就可以通过调整令牌的生成速率和桶大小,来精确的控制流量了。

相关组件

  • qdisc (Queueing Discipline):决定流量如何排队、发送和丢弃
  • class (类):将流量分类到不同的优先级或子队列
  • filter (过滤器):匹配流量的特定属性(如IP、端口等)

问题及解决方案

基于Traffic Control原理,它依然存在如下问题:

  1. Traffic Control只能控制“发包”,而不能直接控制“收包”,这也就说明,我们无法控制下行带宽的速率
  2. 当队列满时,也会发生丢包的情况,我们应该如何避免丢包情况的发生

解决问题一

解决Traffic Control只能控制“发包”的问题,我们在物理网卡eth2和业务之间,增加一个虚拟网卡ifb2,让eth2将数据包通过filter重定向到ifb2上,然后业务使用虚拟网卡ifb2接收数据时,对于ifb2来说,这些数据包就是“发包”流程了,Traffic Control可以直接控制

正常的流程
正常的流程

加入ifb2虚拟网卡后的流程
加入ifb2虚拟网卡后的流程

解决问题二

我们可以适当的增加队列的大小,来尽可能的降低丢包的可能性

使用及配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 安装
yum install iproute

# 先确认使用的是否是eth2,有的机器使用的是eth0,
# 也可以查看/proc/net/dev文件,通过确认那个网卡在收发数据包来确认
ip addr show

# 确认ifb2网卡是否已存在,如果已经存在了,就换个名字
ip addr show | grep ifb2

# 创建虚拟网卡
ip link add dev ifb2 type ifb
ip link set dev ifb2 up
tc qdisc add dev eth2 handle ffff: ingress

# 将eth2的入口流量通过filter的方式重定向给ifb2网卡
tc qdisc add dev eth2 handle ffff: ingress
tc filter add dev eth2 parent ffff: protocol ip u32 \
match u32 0 0 \
action mirred egress redirect dev ifb2

# 确认2个网卡是否存在限制
tc qdisc show dev ifb2
tc qdisc show dev eth2

# 对ifb2进行限流
# rate 表示的生成令牌的平均速率,当前设置的是4Gbps
# burst 表示的是令牌桶的容量,也就是突发流量上限,当前设置的是800Mbps
# latency 数据包在队列中的最大等待时间,当前设置的是50ms
# limit 队列中可存储的最大字节数,当前设置的是100MB
tc qdisc add dev ifb2 root tbf rate 4000mbit burst 800mbit latency 50ms limit 100m

效果校验

单机器网卡流量监控
单机器网卡流量监控

通过上图发现,峰值幅度明显降低

后记

Traffic Control工具复杂且难以把控,可以使用wondershaper来限制网卡流量,这个命令的底层也是使用的Traffic Control,不过它只需要一条命令就够了

1
2
3
wondershaper -a eth2 -d 4000mbit -u 0
# 低版本
wondershaper eth2 0 4000000