过载保护初探

过载原理

过载是指当前负载超出了系统的最大处理能力,例如系统的极限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的算法和限制资源利用率,功能类似,但有不同的使用场景,并且可以和全链路超时机制一起用多加一层防护

负载均衡

负载均衡是锦上添花的策略

指的是上游调用方,当发现下游服务方的节点有些负载较高时,调低这个节点的权值,将请求调整到负载较低的实例上。

这能有效减少整个服务的过载可能性

这就需要一个下游到上游的反馈机制,来反馈负载情况

服务的上游限流

下游限流不能解决最关键的问题,下游虽然在一个劲的收拾垃圾,把超时的请求清理出去

但是架不住过载时,上游不停的发请求过来

这里就能利用负载均衡策略提供的反馈机制

当上游调用方,发现下游服务方的大部分节点都存在负载较高的情况,就直接开始丢弃部分请求,返回繁忙

  • 优点:把问题掐死在较上游的位置

  • 缺点:还不够彻底,当一个服务的调用链非常长时,其实整个调用链已经做了一大段无用功了

服务的接入层限流

这是上段限流的一个拓展

把反馈机制,一层层往上反馈,然后从接入层判断整个服务是否过载,来直接丢弃部分请求来返回繁忙

判断主要还是基于木桶效应,下游的服务哪个所有节点都负载较高,就判断整个服务负载较高,从而丢弃请求

总结

服务的下游限流策略解决了无用功的问题

服务的上游限流策略解决了负载激增时过载的问题,接入层限流策略作为一种扩展,能做的更好