基于缓存的限流,降级和熔断

646 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

基于缓存的限流,降级和熔断

分布式应用,或者微服务开发中经常需要处理的问题是什么?是上下游应用的异常处理,特别是微服务,需要尽力避免由于某个微服务的异常造成的应用整体崩溃,也就是“雪崩”。

首先看一下Spring Cloud 和Hystrix组件的容错流程图 Hystrix

  1. 每个请求都会封装到 HystrixCommand 中
  2. 请求会以同步或异步的方式进行调用
  3. 判断熔断器是否打开,如果打开,它会直接跳转到 8 ,进行降级
  4. 判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8
  5. 如果前面没有错误,就调用 run 方法,运行依赖逻辑
  6. 运行方法可能会超时,超时后从 5a 到 8,进行降级
  7. 运行过程中如果发生异常,会从 6b 到 8,进行降级
  8. 运行正常会进入 6a,正常返回回去,同时把错误或正常调用结果告诉 7 (Calculate Circuit Health)
  9. Calculate Circuit Health它是 Hystrix 的大脑,是否进行熔断是它通过错误和成功调用次数计算出来的
  10. 降级方法(8a没有实现降级、8b实现降级且成功运行、8c实现降级方法,但是出现异常)
  11. 没有实现降级方法,直接返回异常信息回去
  12. 实现降级方法,且降级方法运行成功,则返回降级后的默认信息回去
  13. 实现降级方法,但是降级也可能出现异常,则返回异常信息回去

1.限流

如果一个应用稳定处理外部的请求有一个上限阈值,那么我们就需要为这个应用的调用,设置限流。其实使用线程池就可以实现限流,一个线程池的 最大并发线程数就是,对于超出部分,可以参考线程池的处理策略:

//创建一个定长线程池,支持定时及周期性任务执行。异常策略
public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}


  • 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

  • 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

  • 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务

  • 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

  • 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程

  • 当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

限流策略(defaultHandler)说明
AbortPolicy抛出RejectedExecutionException
DiscardPolicy什么也不做,直接忽略
DiscardOldestPolicy丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
CallerRunsPolicy直接由提交任务者执行这个任务

以上处理方式是通过并发线程和阻塞队列的方式来实现的,而且只能应用在单个节点

常规的分布式限流手段一般是使用Redis来实现,具体流程如下:

  1. 设置一个超时时间为一分钟的key
  2. 获得请求后这个key值++
  3. 如果key值超过一分钟上限则异常处理
  4. 一分钟超时后重建这个key(重复操作1)这样就可以实现一分钟内限流

2.降级

服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证核心功能服务的可用性,都要对某些服务降级处理。比如当双11活动时,把交易无关的服务统统降级,如查看蚂蚁森林,查看历史订单等等。

3.熔断

一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护;服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)


所有的降级和熔断都需要有恢复方案

一般做法和限流一样,设置一个定时过期的key,定时刷新重构这个key并实现服务的自动恢复

要素说明
时间窗口key的生存周期,例如限流一分钟内不超过1000个,一分钟就是限流的时间窗口
触发扳机如上的1000,例如异常返回数,超时数
降级策略拒绝服务,排队,降级返回
恢复窗口例如每2分钟检测一次服务状态是否正常
恢复方案主动检测服务的状态,一般是获取下游服务的状态,正常则重置降级策略,失败则等待执行下次恢复方案

4.回到源头如何判断触发限流,熔断和降级?

  1. 对于单体应用,判断接口或者服务的失败率,超时率,就像hystrix的Calculate Circuit Health 对于集群应用,如何整体的判断集群的压力(失败率,超时率)?实时的读写Redis或者数据库显然是不可行的。 可以参考prometheus/grafana,Turbine 这种集群监控的方式来监控微服务的状态。

  2. 熔断器提供一个get接口后可以获得,熔断器的服务状态,提供一个put接口, 就可以获取的全局的微服务状态,及时的为微服务集群踩刹车或者恢复服务。

4.参考资料

  1. SpringCloud传送门
  2. Golang grafana传送门
  3. Hystrix传送门