熔断器 从Sentinel 1.8.0开始,无论使用DegradeRuleManager的loadRules API还是使用动态数据源加载熔断降级规则,当熔断降级规则更新时,Sentinel会为每个熔断降级规则都创建一个熔断器并与熔断降级规则绑定,并且不同的熔断降级策略对应不同类型的熔断器。 熔断器接口的定义如下。 • getRule:获取熔断器绑定的熔断降级规则。 • tryPass:完成can pass check逻辑,若返回false,则表示拒绝当前请求。 • currentState:获取当前熔断器的状态(OPEN、HALF_OPEN、CLOSED)。 •onRequestComplete:在请求完成时调用,请求完成包括正常完成、被拒绝或发生异常。 对DegradeSlot类的源码也进行了更改,由DegradeSlot在entry方法中遍历熔断器,调用熔断器的tryPass方法;在exit方法中遍历熔断器,调用熔断器的onRequestComplete方法。 抽象熔断器 虽然不同熔断降级策略的熔断器实现逻辑不同,但差异只是阈值的判断不同或需要统计的指标数据不同,而是否放行请求只需要根据当前熔断器的状态判断,因此,Sentinel为不同熔断降级策略的熔断器提供了一个统一的抽象类——AbstractCircuitBreaker。 AbstractCircuitBreaker类定义的字段及构造方法的源码如下。 • rule:熔断器绑定的熔断降级规则,在构造方法中被传入。 • recoveryTimeoutMs:熔断器开启的持续时间,单位为毫秒,对应熔断降级规则配置的timeWindow。 • observerRegistry:熔断器状态改变监听器的注册器,在熔断器发生状态改变时通过注册器获取注册的所有监听器并回调onStateChange方法。 • currentState:记录当前熔断器的状态。 • nextRetryTimestamp:允许熔断器关闭的时间。 nextRetryTimestamp等于熔断器开启时的时间加上recoveryTimeoutMs,源码如下。 updateNextRetryTimestamp方法在熔断器从CLOSED状态变为OPEN状态时或从HALF_OPEN状态变为OPEN状态时被调用。 1. CLOSED→OPEN fromCloseToOpen方法可实现将熔断器从CLOSED状态变为OPEN状态,源码如下。 • 方法参数为触发值,即达到阈值时触发熔断器开启的当前值。 • 方法可实现开启熔断器,更新下一次允许将熔断器关闭的时间,并通知状态改变观察者。 2. HALF_OPEN→OPEN fromHalfOpenToOpen方法可实现将熔断器从HALF_OPEN状态变为OPEN状态,源码如下。 • 方法参数为触发值,即达到阈值时触发熔断器开启的当前值。 • 方法可实现开启熔断器,更新下一次允许将熔断器关闭的时间,并通知状态改变观察者。 3. HALF_OPEN→CLOSED fromHalfOpenToClose方法可实现将熔断器从HALF_OPEN状态变为CLOSED状态,源码如下。 该方法可实现关闭熔断器并重置滑动窗口,重新统计熔断指标数据,最后通知状态改变观察者。其中调用的resetStat方法是一个抽象方法,由子类实现,用于重置滑动窗口。 4. OPEN→HALF_OPEN fromOpenToHalfOpen方法可实现将熔断器从OPEN状态变为HALF_OPEN状态,源码如下。 • 方法参数为调用链上下文。 • 方法可实现将熔断器从OPEN状态变为HALF_OPEN状态,先通知状态改变观察者,再从Context实例中获取当前资源的Entry实例,向Entry实例注册一个exit回调处理器。该处理器在Entry实例的exit方法被调用时回调。 • exit回调处理器实现:如果当前请求被拒绝(不仅包括熔断器拒绝的,也包括限流、系统自适应等拒绝的),将熔断器从HALF_OPEN状态变为OPEN状态。 思考:为什么要在fromOpenToHalfOpen方法中注册exit回调处理器? fromOpenToHalfOpen方法在tryPass方法中被调用。tryPass方法的源码如下。 ① 如果当前熔断器处于CLOSED状态,则放行请求。 ②如果当前熔断器处于OPEN状态,且当前时间大于nextRetryTimestamp,则放行请求,并将熔断器状态改为HALF_OPEN。 ③ 如果当前熔断器处于HALF_OPEN状态,则拒绝请求。 从tryPass方法中可以看出,当熔断器已经处于OPEN状态或已经处于HALF_OPEN状态时,当前请求将被拒绝。特殊地,只有触发熔断器从OPEN状态变为HALF_OPEN状态的那个请求才允许被放行。 注册exit回调处理器用于修复在某种情况下熔断器永远保持半开启状态的Bug,可在GitHub的Sentinel主页下查看编号为1638的Issue对此Bug的描述,如图6.4所示。 图6.4 编号为1638的Issue详情 Bug描述:当请求被其他规则阻止时,熔断器不会从半开启状态中恢复。 例如,同一资源有两个熔断降级规则:R1(熔断器状态=OPEN, recoveryTimeout=10s)和R2(熔断器状态=OPEN,recoveryTimeout=20s)。由于R2比R1的timeWindow时间长,因此在某些情况下,R1已达到recoveryTimeout,但R2还未达到recoveryTimeout,如果此时有请求进来,则熔断器将进行以下转换。 • R1已达到recoveryTimeout,将从OPEN状态变为HALF_OPEN状态。 • R2未达到recoveryTimeout,依然保持OPEN状态。 因此,该请求将会被R1允许,但会被R2拒绝,请求最终被拒绝,熔断器R1的onRequestComplete方法将不会被调用。 当下一个请求进来时,由于R1关联的熔断器状态为HALF_OPEN,请求将会被该熔断器拒绝,导致R1、R2关联的熔断器的onRequestComplete方法都不会被调用,最终导致R1关联的熔断器状态将永远为HALF_OPEN状态。 实际上,当R1之后有任何规则(不仅包括熔断降级规则)阻止请求时,都可能会发生这种情况。 熔断器的onRequestComplete方法是在DegradeSlot#exit方法中调用的,当发生BlockException时,就不会调用熔断器的onRequestComplete方法。DegradeSlot#exit方法的源码如下。 从DegradeSlot#exit方法的源码中可以看出,如果当前请求已经被拒绝,则该资源绑定的所有熔断器的onRequestComplete方法都不会被调用。而熔断器从HALF_OPEN状态变为OPEN状态或从HALF_OPEN状态变为CLOSED状态都是在熔断器的onRequestComplete方法中完成的,当熔断器处于HALF_OPEN状态时,如果当前请求正常,则onRequestComplete方法会将熔断器变为CLOSED状态。 为了解决这个Bug,Sentinel才选择在熔断器变为HALF_OPEN状态时给Entry注册一个exit回调处理器,在发生BlockException时由exit回调处理器将熔断器打开,由后续请求触发熔断器的CLOSED状态或者使其重新变为OPEN状态。 异常熔断器 异常熔断器用于实现ERROR_RATIO、ERROR_COUNT这两种熔断降级策略,因此异常熔断器关心的是异常指标数据。 异常熔断器的字段定义及构造方法的源码如下。 • strategy:熔断降级策略,由于ExceptionCircuitBreaker支持两种熔断降级策略,因此需要strategy区分ERROR_RATIO和ERROR_COUNT。 •minRequestAmount:最小请求数,当当前时间窗口总请求数大于minRequestAmount时,才能判断异常比率或异常总数是否达到阈值。 • threshold:熔断阈值,当strategy配置为ERROR_RATIO时表示异常比率,当strategy配置为ERROR_COUNT时表示异常总数。 • stat:独立收集指标数据的滑动窗口。 异常熔断器创建的滑动窗口只收集异常总数和总请求数,源码如下。 ExceptionCircuitBreaker实现的onRequestComplete方法的源码如下。 ① 如果当前请求异常,则统计异常指标数据。 ② 统计总请求数。 ③ 根据当前时间窗口统计的指标数据是否达到阈值来改变熔断器的状态。 handleStateChangeWhenThresholdExceeded方法的源码如下。 ① 如果当前熔断器状态为HALF_OPEN,则当请求发生异常时重新打开熔断器,否则直接关闭熔断器。 ② 计算异常总数与总请求数,如果总请求数大于minRequestAmount,那么当熔断降级策略为EXCEPTION_RATIO时,若异常总数与总请求数的比值大于熔断降级规则配置的阈值,则开启熔断器;而当熔断降级策略为ERROR_COUNT时,若异常总数大于熔断降级规则配置的阈值,则开启熔断器。 handleStateChangeWhenThresholdExceeded方法调用transformToOpen方法将熔断器状态设置为OPEN,该方法由父类AbstractCircuitBreaker实现,其源码如下。 该方法的作用是根据当前熔断器状态调用不同的方法开启熔断器。 慢请求熔断器 慢请求熔断器用于实现SLOW_REQUEST_RATIO熔断策略,因此慢请求熔断器关心的是耗时指标数据。 慢请求熔断器的字段定义及构造方法的源码如下。 • maxAllowedRt:如果请求耗时超过该值,则将其视为慢请求。 •maxSlowRequestRatio:慢请求比率阈值,如果慢请求总数与总请求数的比值超过该值,则开启熔断器。 •minRequestAmount:最小请求数,即使慢请求比率已经达到阈值,但总请求数小于minRequestAmount的情况下也不能开启熔断器。 • slidingCounter:独立收集指标数据的滑动窗口。 慢请求熔断器创建的滑动窗口只收集慢请求总数和总请求数,源码如下。 ResponseTimeCircuitBreaker实现的onRequestComplete方法的源码如下。 ① 计算当前请求的执行耗时。 ② 统计慢请求总数和总请求数。 ③ 根据当前时间窗口统计的指标数据是否达到阈值来改变熔断器的状态。 handleStateChangeWhenThresholdExceeded方法的源码如下。 ① 如果当前熔断器处于HALF_OPEN状态,那么,若当前请求是慢请求,则开启熔断器,否则直接关闭熔断器。 ② 根据慢请求总数与总请求数计算出当前的慢请求比率,当慢请求比率大于阈值且总请求数大于minRequestAmount时,开启熔断器。 小结 本篇主要分析Sentinel 1.7.x与Sentinel 1.8.0的熔断降级功能的实现原理,并详细介绍了使用熔断器实现熔断降级功能的实现原理,以及Sentinel提供的两种熔断器的实现原理。 本文给大家讲解的内容是深度解析微服务高并发熔断降级 :熔断器下篇文章给大家讲解的内容是深度解析微服务高并发授权与系统自适应 :授权功能的实现原理 感谢大家的支持!