本篇内容:
- Sentinel对性能的消耗如何
- Sentinel工作流程源码分析
- Sentinel熔断降级实现源码分析
- Sentinel对性能的消耗如何
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方法源码就能找到答案,如下图所示。
本文转载自微信公众号「 Java艺术」,可以通过以下二维码关注。转载本文请联系 Java艺术公众号。