本篇内容:
Sentinel
对性能的消耗如何Sentinel
工作流程源码分析Sentinel
熔断降级实现源码分析
Sentinel对性能的消耗如何
从上一篇《Sentinel
限流的核心功能QPS
统计的实现原理》我们了解到,Sentinel
统计QPS
使用的是时间窗口+Bucket
,并且通过循环复用Bucket
以减少对内存的占用,在统计QPS
时,更是利用当前时间戳定位Bucket
,使用LongAdder
统计时间窗口内的请求成功数、失败数、总耗时优化了并发锁,通过定时任务递增时间戳避免每次都使用System
获取当前时间。可以看到Sentinel
在性能方面所做出的努力,Sentinel
尽最大可能降低自身对应用的影响。
Sentinel
会为每个资源(接口)创建一个保存一分钟内时间窗口为1
秒的Bucket
数组以及一个保存一秒钟内以500ms
为时间窗口的Bucket
数组,将这两个数组包装为一个Node
,以统计该接口的请求数据。每个Bucket
记录一个时间窗口内的请求总数、失败总数、总耗时(通过总耗时可计算平均耗时)、被限流或者被熔断的请求总数。
因此,Sentinel
消耗的内存至少是资源总数乘以每个资源对应的Node
占用的内存大小,每个Node
占用的内存大小即为一个大小为2
的Bucket
数组和一个大小为60
的Bucket
数组所占用的内存。
Sentinel工作流程源码分析
Sentinel
通过复用Bucket
降低对内存的消耗,使用LongAdder
降低并发统计数据对性能的消耗,除这些之外,Sentinel
通过责任链模式实现统计、限流、熔断降级等功能,实现局部无锁化。
Sentinel
的基本使用:
Sentinel
实现统计、限流、熔断降级等功能由一个个ProcessorSlot
完成,例如,统计资源当前时间窗口的请求总数、失败总数等由StatisticSlot
完成,判断当前请求是否需要限流由FlowSlot
完成,判断当前请求是否需要熔断降级由DegradeSlot
完成。
这些ProcessorSlot
按照严格的顺序包装成一个链表,比如StatisticSlot
在FlowSlot
之前,FlowSlot
在DegradeSlot
之前。
ProcessorSlot
的entry
方法在接收到客户端请求时或者客户端向服务端发送请求之前被调用,exit
方法则是在服务端处理完请求(包括异常完成)时或者客户端发送请求完成时被调用。每个ProcessorSlot
通过fireEntry
方法或者fireExit
方法向下传递信号。
看过Netty
源码的朋友应该对这种设计模式的使用并不陌生,Netty
也是通过责任链模式将处理请求的Handler
包装为链表,实现局部串行处理请求。但Sentinel
的ProcessorSlot``与Netty
的Handler
有些区别,ProcessorSlot
的exit
方法并不像Netty
那样是从后往前传递的。
我们熟悉的Shiro
也是通过责任链实现(过滤器),所以Sentinel
实现限流、熔断并不难理解。在不考虑集群限流的情况下。当SphU
的entry
方法被调用时,至少会经过StatisticSlot
、FlowSlot
、DegradeSlot
这三个ProcessorSlot
,其时序图如下。
当StatisticSlot
的entry
方法被调用时,由StatisticSlot
根据资源获取资源的Node
,根据当前时间戳从Node
获取当前时间窗口的Bucket
,然后将Bucket
的请求总数自增1
。StatisticSlot
在entry
方法中捕获异常,如果下游的ProcessorSlot
抛出异常为BlockException
或BlockException
的子类,则将Bucket
的限流总数自增1
,否则将Bucket
的异常总数自增1
。
当FlowSlot
的entry
方法被调用时,检查为当前资源配置的限流规则是否满足限流条件,如果满足条件则抛出BlockException
异常,表示当前请求被限流。由于Sentinel
支持集群限流,所以限流的实现上比较复杂,我们暂不讨论。如果是单节点的限流,则实现上与熔断降级的实现差不多,本篇只介绍熔断降级的实现。
当DegradeSlot
的entry
方法被调用时,检查为当前资源配置的熔断降级规则是否满足条件,如果满足条件则抛出DegradeException
异常,表示当前请求被熔断。
Sentinel熔断降级实现源码分析
Sentinel
会为每个资源(ResourceWrapper
)创建一个Node
,用于统计请求数据(请求总数、异常总数、被限流或被熔断总数、总耗时),为限流和熔断降级功能提供支持。
ResourceWrapper
的name
为资源名称,也可以理解是接口url
,但这样理解是不正确的。资源名称在我们配置限流规则或者熔断降级规则时也用到。
ResourceWrapper
的entryType
为流量类型,可取值为IN
和OUT
,IN
表示流入类型,即服务端接收客户端请求;OUT
为流出类型,即客户端向服务端发起请求。
ResourceWrapper
的resourceType
为资源类型,表示是Servlet
还是RPC
、API
网关、数据库等。可见,Sentinel
还支持对数据库的访问限流、熔断。
Sentinel
提供的熔断降级功能,不仅可以在客户端使用,也可以在服务端使用,但一般会放在客户端,用于流量类型为OUT
类型的资源的熔断降级,保证自身不受服务端的影响,不被服务端拖垮。
判断Sentinel
的熔断降级功能是否支持在服务端执行,我们可通过阅读DegradeSlot
的源码,查看是否限制了只有流量类型为EntryType.OUT
时才生效。
当DegradeSlot
的entry
方法被调用时,由DegradeSlot
调用DegradeRuleManager
的checkDegrade
方法检查当前请求是否满足某个熔断降级规则。
在学习如何使用Sentinel
实现熔断降级时,我们是使用DegradeRuleManager
加载我们配置的熔断降级规则的,所以DegradeSlot
将check
逻辑才交给DegradeRuleManager
去完成。
DegradeRuleManager
首先根据资源名称获取配置的熔断降级规则,因为我们可以对同一个资源配置多个熔断降级规则,所以返回的将是一个集合。然后遍历熔断降级规则,调用DegradeRule
的passCheck
方法将检查是否需要触发熔断的逻辑交给DegradeRule
完成。如果对一个资源配置多个熔断降级规则,那么只要有一个熔断降级规则满足条件,就会触发熔断。
DegradeRule
的passCheck
方法源码如下。
从DegradeRule
的passCheck
方法的源码中,我们并未发现有任何地方限制熔断降级的触发只有流量类型为EntryType.OUT
才生效,因此,熔断降级不仅可以用于客户端,也可以用于服务端。
熔断降级策略支持三种:
- 1、平均响应时间 (
DEGRADE_GRADE_RT
) - 2、异常比例 (
DEGRADE_GRADE_EXCEPTION_RATIO
) - 3、异常数 (
DEGRADE_GRADE_EXCEPTION_COUNT
)。
官方文档在介绍DEGRADE_GRADE_EXCEPTION_COUNT
策略的地方加了使用注意说明:注意由于统计时间窗口是分钟级别的,若timeWindow
小于60s
,则结束熔断状态后仍可能再进入熔断状态。
这句话并不难理解,从DegradeRule
的passCheck
方法源码就能找到答案,如下图所示。
END
本篇分析的是Sentinel
的核心实现原理,Sentinel
也提供了各种适配模块,用于接入Spring MVC
,Dubbo RPC
以及API GATEWAY
网关等,而适配器实现的无非就是拦截各种请求或者各种发送请求。