过载保护初探
过载原理
过载是指当前负载超出了系统的最大处理能力,例如系统的极限 QPS 是 100,但实际的请求达到了 1000QPS
对一个完全没有过载保护的系统来说,原本是 0.01 秒完成的请求,并不是直觉上皆大欢喜的 0.1 秒完成
而是一部分请求稍微延长了请求时间,而另外一部分请求超时很多秒
这种现象的本质原因,是因为不管是基于 epoll 的异步调度模型,还是 golang 的协程模型等等
网络,CPU,内存,磁盘不可能完全的分配均衡
一个请求在到达服务游时,就可能会因为这种不均衡的调度产生饥饿现象,等待很久。
更糟糕的是因此导致的雪崩现象
大量处理无用功(单个后台服务雪崩)
饥饿的请求也会去被处理,但是处理的时候已经超时很久了,客户端可能已经断开了请求 (实际的客户因为不耐烦或是上游超时断开),此时请求即使处理了也是无用功
用户失败大量重试(整个服务雪崩)
失败的客户端多次重试,使得请求量急剧增加,产生雪崩现象
这种加剧是灾难式的,后台的过载保护不到位,使得前面的服务链过载,从而导致整个服务崩溃
可以看到,从宏观上看,过载需要防护的是三个点
超时请求处理的无用功
失败造成的重试,使得负载激增,导致过载
真的存在这么大的访问量,负载激增,导致过载
过载保护策略
服务的下游限流
根据 QPS 进行限制
把 QPS 限制在一个不高的水平,就能有效的防止过载现象
最简单的(错误的)算法
这种算法是直觉上的,一段时间设置一个计数器,如果计数器大于某个值,就返回繁忙,过了这一段时间就重置计数器
但是仔细一想问题就大了,例如限定 qps 是 100,重置计数器的时间是 1 秒
然后在 0.9-1 秒时通过了 100 个请求,然后重置计数器
在 1-1.1 秒时又通过了 100 个请求,那么在这 0.2 秒内通过了 200 个请求,而不是限定的 1 秒内最多 100 个请求
这个算法的问题是无论你把时间间隔设置的多短,总有更短的单位时间会超出这个限定值,因为这种重置的设定不够平滑
平滑的算法已经很成熟了,漏桶和令牌桶
漏桶
假设请求到达时,往桶里加水,而桶会以恒定速度漏水,当加水太快,桶就满了,此时返回繁忙
令牌桶
令牌桶算法以一个恒定的速度往桶里放令牌,当请求到达时,从桶里取走一个令牌,当没有令牌可以取走时,返回繁忙
看别人的资料说漏桶和令牌桶算法是略有不同的,但是我觉得就是一个漏水一个反过来是进水,没看出来区别
优点:算法现成的,实现简单
缺点:QPS 阀值的设定需要经验,更可能的是需要实际的性能测试,最关键的是一旦代码发生改动,需要重改
根据系统资源进行限制
系统资源包括:cpu,内存,磁盘,网络,根据系统资源的监控,判断达到阀值,此时返回繁忙
优点:不需要进行太多经验来设定,更不需要各种测试来设定限定值
缺点:有些情况下判断较为复杂,例如对一个自己管理内存的程序来说,内存吃满不一定是过载,他可能是自己申请了一大块内存去自己管理内存
或者对于多进程和多线程服务来说,单核的 CPU 跑满不一定是过载,但也有可能是过载。
超时机制
在协议中加入一个时间戳字段
客户端发送请求时记录下时间戳,当服务游进行处理时判断这个时间戳是否超过一定时间,如果超过,那就返回超时
优点:完全不需要任何配置,每个服务都可以配置相同的超时时间
缺点:
a 需要 NTP 同步,这倒不是什么大问题,crontab 下就行了
b 对单个服务来说,这个请求可能没有超时,但是对整个链路来说,可能已经超时很久了
全链路超时机制
全链路超时是基于以上超时机制的补全
在整个调用链上,加上时间戳字段,由最前的接入层,打上时间戳,每一个下游服务在处理请求时都需要判断这个时间戳的是否超时
小结
限流策略主要还是防护无用功的问题,并不能解决另外两个问题
从功能性来看,限制 QPS 的算法和限制资源利用率,功能类似,但有不同的使用场景,并且可以和全链路超时机制一起用多加一层防护
负载均衡
负载均衡是锦上添花的策略
指的是上游调用方,当发现下游服务方的节点有些负载较高时,调低这个节点的权值,将请求调整到负载较低的实例上。
这能有效减少整个服务的过载可能性
这就需要一个下游到上游的反馈机制,来反馈负载情况
服务的上游限流
下游限流不能解决最关键的问题,下游虽然在一个劲的收拾垃圾,把超时的请求清理出去
但是架不住过载时,上游不停的发请求过来
这里就能利用负载均衡策略提供的反馈机制
当上游调用方,发现下游服务方的大部分节点都存在负载较高的情况,就直接开始丢弃部分请求,返回繁忙
优点:把问题掐死在较上游的位置
缺点:还不够彻底,当一个服务的调用链非常长时,其实整个调用链已经做了一大段无用功了
服务的接入层限流
这是上段限流的一个拓展
把反馈机制,一层层往上反馈,然后从接入层判断整个服务是否过载,来直接丢弃部分请求来返回繁忙
判断主要还是基于木桶效应,下游的服务哪个所有节点都负载较高,就判断整个服务负载较高,从而丢弃请求
总结
服务的下游限流策略解决了无用功的问题
服务的上游限流策略解决了负载激增时过载的问题,接入层限流策略作为一种扩展,能做的更好
未找到相关的 Issues 进行评论
请联系 @tedcy 初始化创建