本文已参与「新人创作礼」活动,一起开启掘金创作之路。
基于缓存的限流,降级和熔断
分布式应用,或者微服务开发中经常需要处理的问题是什么?是上下游应用的异常处理,特别是微服务,需要尽力避免由于某个微服务的异常造成的应用整体崩溃,也就是“雪崩”。
首先看一下Spring Cloud 和Hystrix组件的容错流程图
- 每个请求都会封装到 HystrixCommand 中
- 请求会以同步或异步的方式进行调用
- 判断熔断器是否打开,如果打开,它会直接跳转到 8 ,进行降级
- 判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8
- 如果前面没有错误,就调用 run 方法,运行依赖逻辑
- 运行方法可能会超时,超时后从 5a 到 8,进行降级
- 运行过程中如果发生异常,会从 6b 到 8,进行降级
- 运行正常会进入 6a,正常返回回去,同时把错误或正常调用结果告诉 7 (Calculate Circuit Health)
- Calculate Circuit Health它是 Hystrix 的大脑,是否进行熔断是它通过错误和成功调用次数计算出来的
- 降级方法(8a没有实现降级、8b实现降级且成功运行、8c实现降级方法,但是出现异常)
- 没有实现降级方法,直接返回异常信息回去
- 实现降级方法,且降级方法运行成功,则返回降级后的默认信息回去
- 实现降级方法,但是降级也可能出现异常,则返回异常信息回去
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来实现,具体流程如下:
- 设置一个超时时间为一分钟的key
- 获得请求后这个key值++
- 如果key值超过一分钟上限则异常处理
- 一分钟超时后重建这个key(重复操作1)这样就可以实现一分钟内限流
2.降级
服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证核心功能服务的可用性,都要对某些服务降级处理。比如当双11活动时,把交易无关的服务统统降级,如查看蚂蚁森林,查看历史订单等等。
3.熔断
一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护;服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
所有的降级和熔断都需要有恢复方案
一般做法和限流一样,设置一个定时过期的key,定时刷新重构这个key并实现服务的自动恢复
| 要素 | 说明 |
|---|---|
| 时间窗口 | key的生存周期,例如限流一分钟内不超过1000个,一分钟就是限流的时间窗口 |
| 触发扳机 | 如上的1000,例如异常返回数,超时数 |
| 降级策略 | 拒绝服务,排队,降级返回 |
| 恢复窗口 | 例如每2分钟检测一次服务状态是否正常 |
| 恢复方案 | 主动检测服务的状态,一般是获取下游服务的状态,正常则重置降级策略,失败则等待执行下次恢复方案 |
4.回到源头如何判断触发限流,熔断和降级?
-
对于单体应用,判断接口或者服务的失败率,超时率,就像hystrix的Calculate Circuit Health 对于集群应用,如何整体的判断集群的压力(失败率,超时率)?实时的读写Redis或者数据库显然是不可行的。 可以参考prometheus/grafana,Turbine 这种集群监控的方式来监控微服务的状态。
-
熔断器提供一个get接口后可以获得,熔断器的服务状态,提供一个put接口, 就可以获取的全局的微服务状态,及时的为微服务集群踩刹车或者恢复服务。