背景:
在业务服务中,我们引入了熔断功能来保障业务的高可用。比如登录接口里,会去调用其它服务,但是这个服务又不是必须的,不能因为服务有问题,例如平常是10ms响应,故障时间段变成设置的超时时间比如100ms,导致接口响应变差。虽然我们能容忍超时,但是不能接受一直持续着,这相当于会一直耗一个超时时间。所以熔断在这里解决的问题就是如果持续故障,就在一个时间周期内不再调用,过了周期再调用服务检查恢复没有。
那熔断的逻辑不难实现,各种编程语言也都有开源库实现。麻烦的是要验收这个功能是正常的,能达到预期的熔断效果,我们总不能上线功能等到故障发生再验收效果,万一没实现那岂不又是个bug,返工还不一定能确保没问题。
想模拟超时也不难,比如我可以把配置的超时改小就很容易超时了,但模拟不了超时恢复的效果。或者改代码概率性超时,但这已然不是之后发到线上的版本了,发布还要再改回来,不符合测试的标准。我们应该要在不改配置和代码的情况下,真实地模拟超时的发生和恢复。那怎样才能模拟到真实的场景,那就是程序发出去的网络包到回来这个时间段变长了或恢复正常了,跟程序无关。
模拟:
我们以ping baidu.com
为例子,看实现的效果
从截图可以看出,开始网络包使用40ms+就可以完成来回,然后我们设置了延迟,增加了200ms的时延,之后再取消又恢复成40ms+。
那这里用到的模拟超时工具就是Linux的Tc(Traffic Control
)工具。
tc
是 Linux 系统中的一个命令行工具,用于控制网络流量和队列。它可以帮助用户限制网络带宽、设置延迟、进行拥塞控制等操作,从而优化网络性能和提高用户体验。
tc
的原理比较简单,它主要是通过设置队列规则和调度算法来实现网络流量的控制。用户可以使用 tc
命令创建和修改队列规则,包括创建队列、设置带宽和延迟、定义过滤规则等等。然后,qdisc
(队列调度器)将根据这些规则对网络数据包进行分类和处理,以确保网络流量的公平分配和合理利用。
我们先看使用的一组命令是什么,然后再分别介绍命令的作用
sql复制代码tc qdisc add dev eth0 root handle 1: htb
tc class add dev eth0 parent 1: classid 1:10 htb rate 100Mbit
tc qdisc add dev eth0 parent 1:10 handle 10:1 netem delay 200ms
tc filter add dev eth0 parent 1: protocol ip u32 match ip dst 39.156.66.10 flowid 1:10
tc filter del dev eth0
网络包是程序调用系统的socket到内核态,分装成tcp/udp包,最后通过网卡发出去的。
那么Tc就是在网络包从网卡发出去前作用的,控制流量的调度、延迟等。
csharp复制代码tc qdisc add dev eth0 root handle 1: htb
在eth0网卡上添加一个HTB(Hierarchical Token Bucket)队列规则,使用ID(1:)标示,root表示这个是根规则,用于管理所有其他规则,默认会生成一个默认队列(图中名称root队列),流量调度为FIFO(先进先出),不做任何处理,直接将数据包发出去。
tc class add dev eth0 parent 1: classid 1:10 htb rate 100Mbit
这时候在eth0上创建了一个htb类型的分层队列,绑定到上层 【parent 1:】1:就是上个命令创建的ID,设置自己ID为 【classid 1:10】,【rate 100Mbit】指的是该class的最大带宽限制,即该class的带宽上限为100Mbps(兆比特每秒)。
100Mbps是指每秒钟可以传输的数据量,其单位为兆比特每秒(Mbps)。也就是说,如果网络中的所有设备都支持100Mbps的传输速率,那么在该网络中,每秒钟可以传输100兆比特的数据。这个数字通常用来表示网络的带宽大小。带宽越大,网络传输速度越快,可以支持更多的设备同时在线和更高的数据传输需求。在网络工程中,了解网络带宽是非常重要的,因为它可以帮助您更好地规划和管理网络资源。
这时候流量还是默认走root队列。因为在Linux的tc工具中,默认情况下,所有未匹配到的流量都会被发送到root队列中。
从图中可以看出,带宽的控制是通过生成了一个内存的令牌桶,平均生存速率为100Mbps(约等于1.49MB/s),该队列将被限制在100Mbps的速率下。此时,令牌桶算法会根据该速率来生成令牌,并根据令牌数来控制流量的发送速率。如果一个数据包的大小超过了令牌桶中可用的令牌数量(默认10KB,约等于0.01MB),那么该数据包将被缓存,并等待更多的令牌生成。这样可以保证数据包的发送速率不会超过设定的速率上限。
这时候还只是限制了带宽上限,100Mbps还不足以让我们出现丢包,还需要额外设置我们期望的超时时间。
那么看下一个命令,给包设置延迟时间
arduino复制代码tc qdisc add dev eth0 parent 1:10 handle 10:1 netem delay 200ms
通过qdisc来对队列进行管理,绑定到上层 【parent 1:10】1:10就是上个命令创建的classid,设置自己ID为 【10:1】设置netem模块来控制网络包的延迟时间,也可以设置丢包率。netem使用Linux内核中的红黑树结构来实现延迟队列,延迟时间最短的数据包会排在红黑树最前面,通过检查过期时间来决定是否将数据包发送出去。
我们的准备工作已经做的差不多了,只要数据包进来就会受到200ms延迟的影响。那最后一步就是匹配我们的目标流量,也就是案例中baidu.com对应的目标IP数据包。
sql复制代码tc filter add dev eth0 parent 1: protocol ip u32 match ip dst 39.156.66.10 flowid 1:10
通过filter设置目标IP匹配,绑定到根规则上 【parent 1:】,也就是说作用在第一层进行规则匹配,然后规则匹配的是IP协议,使用过滤器u32进行匹配,目标的IP为 39.156.66.10(这个IP是ping baidu.com开始时候拿到的IP,以实际拿到的为准),最后让匹配到的流量转发给cliassid为1:10的流量类别。
整个准备工作就完成了,此时观察网络包是否如预期设定的出现延迟即可。
最后测试完了也别忘了将配置给取消掉,免得环境一直出现延迟的问题。
python复制代码tc filter del dev eth0 ; 删除filter规则
tc class del dev eth0 classid 1:10 ;删除指定类别的
tc qdisc del dev eth0 root ;删除整个配置