3.6:服务熔断设计
本文将梳理微服务架构下,服务熔断原理与设计。整体包含以下两部分:
- 为什么需要服务熔断
- Hystrix熔断的设计
为什么需要服务熔断
熔断场景
先定义两种角色,上游服务(服务调用方),下游服务(服务发布方)。一般而已,上游服务和下游服务是分开部署的,上游服务通过远程调用的方式来使用下游服务功能。
基于网络通信过程中的不确定性,有时上游服务调用下游服务可能出现如下四种情况:成功、失败、超时、拒绝。
在出现失败、超时、拒绝时,若不做处理,将有可能出现以下几种问题:
- 因单个服务故障导致系统雪崩,上游服务调用下游服务超时超时将增加请求的RT(Response Time),在系统高峰期请求将阻塞在上游服务进而导致上游服务也出现故障。
- 请求响应的结果是不可控的,用户收到的可能是超时、失败、拒绝等不可理解的错误信息。
因此在这种情况下,上游服务要有能力来对出现问题的下游服务进行熔断,也就是对有问题的下游服务不进行访问,而直接将将结果进行降级处理。
熔断设计
基于对场景的分析,可以知道熔断最基本的几个功能:
- 需要知道下游服务的调用结果。
- 可以根据调用结果判断是否开启熔断。
- 自定义降级内容,某个服务或接口开启熔断后,所有对该服务的请求都不在调用下游服务,而直接返回自定义的降级内容。
- 自动关闭熔断,下游服务不会一直处于故障状态,在下游恢复后需要能自动的关闭熔断。
下面我们就来看看Hystrix是怎么设计实现熔断的把。
Hystrix熔断的设计
Hystrix不仅仅是熔断器,他是专门用来保护服务调用过程中的各种问题。主要包括了服务线程池隔离,请求缓存,请求合并,以及最重要的熔断和降级。
本文重点分析熔断和降级的设计,其他内容请自行了解。
指标衡量工具:HystrixCommandMetrics
在Hystrix中,每一个Command对应一个断路器,每个断路器都有对应的HystrixCommandMetrics来负责结果统计、熔断的判断。
在HystrixCommandMetrics中最核心的功能就是维护了滑动时间窗口(t)内的若干个桶(n),每个桶统计(t/n)秒内的统计结果。默认t=1s,n=10。
熔断结果统计设计图如上图所示,每个桶有四个统计值,分别是:
- 当前时间窗口内请求总数量。
- 当前时间窗口内请求失败数量。
- 当前时间窗口内请求拒绝数量。
- 当前时间窗口内请求超时数量。
断路器主要使用HystrixCommandMetrics下面两个方法来判断是否打开。
- HystrixCommandMetrics.HealthCounts.getTotalRequests():请求总数
- HystrixCommandMetrics.HealthCounts.getErrorPercentage():请求错误比例
<font color=red>
虽然HystrixCommandMetrics维护了一个滑动时间窗口内的n个桶数据,但是Hystrix却只使用了最新一个桶来作为判断的依据。
</font>
断路器:HystrixCircuitBreaker
HystrixCircuitBreaker是熔断实现的核心类,包括四个重要属性:
- HystrixCommandProperties properties:断路器配置信息
- HystrixCommandMetrics metrics:当前断路器维护的指标衡量工具
- AtomicBoolean circuitOpen:熔断开关
- AtomicLong circuitOpenedOrLastTestedTime:熔断开关打开时间或上一次探测时间
断路器的所有方法都是围绕上述四个属性来实现的,下面我们就详细分析下断路器的核心方法
allowRequest()
Hystrix在调用下游服务之前,首先调用该方法判断是否允许请求,若返回false,则直接走降级逻辑(Fallback)。
下面我们分析下allowRequest()的主要流程:
-
熔断开关是否强制打开,是则返回false,否则走下一步。
-
熔断开关是否强制关闭,是则返回true,但是返回true之前还是会调isOpen()走断路器的计算逻辑,用来模拟熔断打开/关闭行为。
-
调isOpen()和allowSingleTest()方法,这里代码(!isOpen() || allowSingleTest())有的拗口,总结下就两种情况:
熔断开关是关闭时,则allowRequest()直接返回true,允许请求。 熔断开关是打开的,则走allowSingleTest()逻辑,检查当前是否可以进行下一次探测了。
isOpen()
该方法专门用来判断断路器是否打开,具体流程如下:
- 熔断开关(circuitOpen)是否打开,也就是已经处于熔断状态了,则直接返回true。
- 获取最新一个统计桶。
- 判断请求总量是否小于设置的阈值,是则返回false。
- 判断错误率是否小于设置的阈值,是则返回false,否则进入下一步。
- 到这里说明需要打开熔断开关了,通过CAS来设置熔断开关为true。设置成功,则将circuitOpenedOrLastTestedTime设置为当前时间。
- 返回熔断开关为打开。
allowSingleTest()
该方法主要是在熔断开关打开的状态下,不断的在达到了探测时间时,临时放行一个请求到下游服务。主要就以下两步:
- 判断(circuitOpenedOrLastTestedTime+设置的探测时间)是否小于当前时间,是则说明已经可以进行下一次探测(临时放行这次请求)了。
- 在上一步为是时,重新更新circuitOpenedOrLastTestedTime的值,以供下次使用。
markSuccess()
在每次调用下游服务成功后,都调用该方法来将熔断开关关闭。主要以下两步:
- 若熔断开关是打开的,则将开关关闭。
- 重置指标衡量数据。
Hystrix的熔断设计还是比较清晰的,主要代码里面使用了大量的RxJava类库,不熟悉的话看代码还是比较吃力。
最后我们看下官方提供的熔断器判断流程图:
服务熔断设计流程图