本文共 3407 字,大约阅读时间需要 11 分钟。
简介
Sentinel提供了丰富的限流、熔断功能。它支持控制台配置限流、熔断规则,支持集群限流,并可以将相应服务调用情况可视化。
目前已经有很多项目接入了Sentinel,而本文主要是对Sentinel的限流功能做一次详细的分析。Sentinel会进行流量统计,执行流量控制规则。而统计数据的展示和规则的设置在 sentinel-dashboard 项目中,这是一个 Spring MVC 应用,有后台管理界面,我们通过这个管理后台和各个应用进行交互。
当然,你不一定需要 dashboard,仅仅使用 sentinel-core,它会将统计信息写入到指定的日志文件中,通过该文件内容来了解每个接口的流量情况。这时只是使用到了 Sentinel 的流量监控功能。dashboard 应用默认不持久化数据,它的所有数据都在内存,所以 dashboard 重启意味着所有数据都会丢失。你应按需要定制化 dashboard,如至少你应该要持久化规则设置,QPS 数据非常适合存放在时序数据库中,当然如果你的数据量不大,存 MySQL 也问题不大,定期清理一下过期数据即可,因为大部分人应该不会关心一个月以前的 QPS。
- Sentinel 的核心将不同 Slot 按序串在一起(责任链模式),从而将不同功能(限流、降级、系统保护)组合在一起。核心结构:
Sentinel 的数据统计
数据统计模块的内容,这样读者在后面看到相应的内容的时候心里有一些底。这节内容还是比较简单的,当然,如果你希望立马进入 Sentinel 的主流程,可以先跳过这一节。
Sentinel 的定位是流量控制,它有两个维度的控制,一个是控制并发线程数,另一个是控制 QPS,它们都是针对某个具体的接口来设置的,其实说资源比较准确,Sentinel 把控制的粒度定义为 Resource。
要做控制,首先就要先做统计,它要知道当前接口的 QPS 和并发是多少,进而判断一个新的请求能不能让它通过。
数据统计的代码在StatisticNode
QPS 数据使用了滑动窗口:- 保存最近60秒的统计信息。 windowLengthInMs 故意设置为1000毫秒,表示每秒每个桶,这样我们就可以获得每秒的准确统计信息。
- 线程数量的计数器,即统计并发量
可知,Sentinel 统计了 秒 和 分 两个维度,现在看其实现类
ArrayMetric
Sentinel中的基本度量标准类,使用内部 BucketLeapArray。
属性
- 以分钟维度统计的使用来说,使用子类 BucketLeapArray 实现。
构造器
LeapArray
字段
-
条件(谓词)更新锁,仅在不使用当前桶时使用。
-
内部核心数组 array,它的长度为 60,就是有 60 个窗口,每个窗口长度为 1 秒,一分钟走完一轮。
然后下一轮开启“覆盖”操作。
每个窗口是一个 WindowWrap 类实例。
添加数据的时候,先判断当前走到哪个窗口了(当前时间(s) % 60 即可),然后需要判断这个窗口是否是过期数据,如果是过期数据(窗口代表的时间距离当前已经超过 1 分钟),需要先重置这个窗口实例的数据。
统计数据同理,如统计过去一分钟的 QPS 数据,就是将每个窗口的值相加,当中需要判断窗口数据是否是过期数据,即判断窗口的 WindowWrap 实例是否是一分钟内的数据。
核心逻辑都封装在了 currentWindow(long timeMillis) 和 values(long timeMillis)方法中。
添加数据的时候,我们要先获取操作的目标窗口,也就是
分维度数据统计
currentWindow
Sentinel 在这里处理初始化和过期重置的情况
获取数据,使用的是values
- 返回“有效”窗口中的数据
- isWindowDeprecated
案例
红色部分的Context 代表一个调用链的入口,Context 实例设置在 ThreadLocal,所以它是跟着线程走的,如果要切换线程,需要手动切换。
ContextUtil#enter 有俩参数:-
context name
调用链的入口,以区分不同调用链路,默认是 -
origin
调用方标识,作用- 黑白名单的授权控制
- 统计诸如从应用 application-a 发起的对当前应用 interfaceXxx() 接口的调用,目前这个数据会被统计,但是 dashboard 中并不展示
进入 BlockException 异常分支,代表该次请求被流量控制规则限制,一般会让代码走入到熔断降级逻辑。当然,BlockException 其实有好多个子类
亦可 catch 具体子类处理。
SphU#entry 方法的参数:
-
第一个参数
标识资源,通常就是我们的接口标识,对于数据统计、规则控制等,我们一般都是在这个粒度上进行的,根据这个字符串来唯一标识,它会被包装成 ResourceWrapper 实例。 -
第二个参数
标识资源的类型- EntryType.IN 入口流量,比如我们的接口对外提供服务,那通常就是控制入口流量
- EntryType.OUT 默认就是出口流量,它的业务需要调用订单服务,像这种情况,压力其实都在订单服务,那就指定它为出口流量。
流量类型在 SystemSlot 类中用以实现自适应限流,根据系统健康状态来判断是否要限流,如果是 OUT 类型,由于压力在外部系统中,所以就不需要执行该规则。
若在一个方法中写,要注意内层的 Entry 先 exit,才能做外层的 exit,否则会抛出异常。源码角度来看,是在 Context 实例中,保存了当前的 Entry 实例。
源码解析
ContextUtil
static 代码块
这里会添加一个默认的 EntranceNode 实例。
enter
该行代码可不写,通常情况下,都不会显示设置 context。
ContextUtil.enter("user-center", "app-A");
如果不显式调用该方法,就会进入到默认 context。
然后上面的这个方法会走进ContextUtil#trueEnter
,添加名为 “user-center” 的 EntranceNode 节点: 若不显式调用 ContextUtil#enter
,那 root 就只有一个默认节点 sentinel_default_context
。
context,线程执行的上下文,在 Sentinel 中对于一个新的 context name,Sentinel 会往树中添加一个 EntranceNode 实例。所以它的作用是为了区分调用链路,标识调用入口。在 sentinel-dashboard 中,我们可以很直观地看出调用链路:
SphU
entry
CtSph#entryWithPriority
lookProcessChain(resourceWrapper)
链中每一个节点是一个 Slot 实例,这个链通过 BlockException 异常来告知调用入口最终的执行情况。
Sentinel 提供了 SPI 端点,让我们可以自己定制 Builder,如添加一个 Slot 进去。
由于 SlotChainBuilder 接口设计,我们只能全局所有的 resource 使用相同的责任链配置。
按照默认的 DefaultSlotChainBuilder 生成的责任链继续源码。
对相同的 resource,使用同一责任链实例,不同 resource,使用不同责任链实例。 resource 实例根据 resource name 来判断,和线程没有关系。参考
- https://juejin.cn/post/6906302891875647495
- https://github.com/alibaba/Sentinel/wiki/Sentinel-%E6%A0%B8%E5%BF%83%E7%B1%BB%E8%A7%A3%E6%9E%90
- https://www.javadoop.com/post/sentinel
转载地址:https://javaedge.blog.csdn.net/article/details/106090735 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!