vlambda博客
学习文章列表

你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

前言

之前介绍了Sentinel基本的应用,以及对Sentinel的改造;今天老顾来介绍Sentinel的源码,可以让我们对Sentinel机制会更加深入的了解。

我们知道可以通过Sentinel控制台进行降级限流的规则设置,也可以通过Api的方式进行设置,之前文章介绍过通过Api方式进行降级限流设置。

本质上Sentinel控制台进行设置的,最终也是通过Api进行设置的。

到底针对哪些请求/方法进行规则限制,Sentinel提供两种埋点方式:

1)try-catch 方式(通过 SphU.entry(...)),用户在 catch 块中执行异常处理 / fallback。

Entry entry = null;try {   entry = SphU.entry(KEY); //定义执行名称 //todo 业务代码   System.out.println("entry ok...");} catch (BlockException e1) {   // 降级、限流异常   // todo fallback处理} catch (Exception e2) {// 业务异常 exception} finally {   if (entry != null) {       entry.exit();  }}

2)if-else 方式(通过 SphO.entry(...)),当返回 false 时执行异常处理 / fallback

Entry entry = null;if (SphO.entry(KEY)) { //todo 业务代码     System.out.println("entry ok");} else {   // 降级、限流异常   // todo fallback处理}

针对不同的应用,Sentinel提供了不同的adapter适配器

你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

不同adapter最主要就是要实现埋点,本质就是用上面的埋点Api,只要引入对应的adapter就能够达到基本常用的埋点了,不需要我们自行去定义了。



工作原理

在Sentinel里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个Entry对象。

Entry可以通过对主流框架的适配自动创建(就是上面说的adapter),也可以通过注解的方式或调用 SphU API 显式创建。

slot插

Entry创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如默认情况下会创建一下8个插槽:

  • NodeSelectorSlot负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;

  • ClusterBuilderSlot则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;

  • LogSlot就是打印异常日志

  • StatisticSlot则用于记录、统计不同纬度的 runtime 指标监控信息;

  • AuthoritySlot则根据配置的黑白名单和调用来源信息,来做黑白名单控制;

  • SystemSlot则通过系统的状态,例如 load1 等,来控制总的入口流量

  • FlowSlot则用于根据预设的限流规则以及前面slot统计的状态,来进行流量控制;

  • DegradeSlot则通过统计信息以及预设的规则,来做熔断降级;

    注意:这里的插槽链都是一一对应资源名称的


    对应的插槽Sentinel源码配置

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    上面介绍的插槽是Sentinle重要的概念,还有一个重要的概念node,我们来说明一下。

    Node节点

    node中保存了资源的实时统计数据,例如:passQps,blockQps,rt等实时数据。正是有了这些统计数据后,sentinel才能进行限流、降级等一系列的操

    作。

    node是一个接口,他有一个实现类:StatisticNode,但是StatisticNode本身也有两个子类,一个是DefaultNode,另一个是ClusterNode,DefaultNode又有一个子类叫EntranceNode。

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    其中entranceNode是每个上下文的入口,该节点是直接挂在root下的,是全局唯一的,每一个context都会对应一个entranceNode。另外defaultNode是记录当前调用的实时数据的,每个defaultNode都关联着一个资源和clusterNode,有着相同资源的defaultNode,他们关联着同一个clusterNode。

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    Metric

    metric是sentinel中用来进行实时数据统计的度量接口,node就是通过metric来进行数据统计的。而metric本身也并没有统计的能力,他也是通过Window来进行统计的。

    Metric有一个实现类:ArrayMetric,在ArrayMetric中主要是通过一个叫WindowLeapArray的对象进行窗口统计的

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    滑动窗口

    我们下面看看Sentinel核心的数据统计是怎么做的,如何达到高性能的统计?核心就是利用了滑动时间窗口的巧妙的设计。

    时间窗口是用WindowWrap对象表示的,其属性如下

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    sentinel时间基准由tick线程来做,每1ms更新一次时间基准,逻辑如下:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    sentinel默认有每秒和每分钟的滑动窗口,对应的LeapArray类型,它们的初始化逻辑是:

    protected int windowLengthInMs; // 单个滑动窗口时间值protected int sampleCount; // 滑动窗口个数protected int intervalInMs; // 周期值(相当于所有滑动窗口时间值之和)
    public LeapArray(int sampleCount, int intervalInMs) { this.windowLengthInMs = intervalInMs / sampleCount;    this.intervalInMs = intervalInMs;    this.sampleCount = sampleCount;         this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount);}

    Sentinel提供了2个维度,一个是秒级别、一个分钟级别

    针对每秒滑动窗口,windowLengthInMs=500,sampleCount=2,intervalInMs=1000

    针对每分钟滑动窗口,windowLengthInMs=1000,sampleCount=60,intervalInMs=60000

    对应代码:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    currentTimeMillis时间基准(tick线程)每1ms更新一次,通过currentWindow(timeMillis)方法获取当前时间点对应的WindowWrap对象,然后更新对应的各种指标,用于做限流、降级时使用。


    画图理解

    我们拿每秒维度举个例子,

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    初始的时候arrays数组中只有一个窗口(可能是第一个,也可能是第二个),每个时间窗口的长度是500ms,这就意味着只要当前时间与时间窗口的差值在500ms之内,时间窗口就不会向前滑动。当前窗口current  window还指向Arrays的第一个窗口。例如,假如当前时间走到300或者500时,当前时间窗口current window仍然是相同的那个

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    时间继续往前走,当超过500ms时,时间窗口就会向前滑动到下一个,这时就会更新当前窗口的开始时间(windowStart):

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    时间继续往前走,只要不超过1000ms,则当前窗口不会发生变化:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    当时间继续往前走,当前时间超过1000ms时,就会再次进入下一个时间窗口,此时arrays数组中的窗口将会有一个失效,会有另一个新的窗口进行替换:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    以此类推随着时间的流逝,时间窗口也在发生变化,在当前时间点中进入的请求,会被统计到当前时间对应的时间窗口中。计算qps时,会用当前采样的时间窗口中对应的指标统计值除以时间间隔,就是具体的qps。具体的代码在StatisticNode中:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    上面用图的方式介绍了,滑动窗口时间。这边在提供一份网上模拟滑动窗口给的代码:

    public static void main(String[] args) throws InterruptedException {    int windowLength = 500;     int arrayLength = 2;    calculate(windowLength,arrayLength);        Thread.sleep(100);    calculate(windowLength,arrayLength);        Thread.sleep(200);    calculate(windowLength,arrayLength);        Thread.sleep(200);    calculate(windowLength,arrayLength);        Thread.sleep(500);    calculate(windowLength,arrayLength);        Thread.sleep(500);    calculate(windowLength,arrayLength);        Thread.sleep(500);    calculate(windowLength,arrayLength);        Thread.sleep(500);    calculate(windowLength,arrayLength);        Thread.sleep(500);    calculate(windowLength,arrayLength);    } private static void calculate(int windowLength,int arrayLength){    long time = System.currentTimeMillis();     long timeId = time/windowLength;        long currentWindowStart = time - time % windowLength;        int idx = (int)(timeId % arrayLength); System.out.println("time="+time+",currentWindowStart="+currentWindowStart+",timeId="+timeId+",idx="+idx);}

    这里假设时间窗口的长度为500ms,数组的大小为2,当前时间作为输入参数,计算出当前时间窗口的timeId、windowStart、idx等值。执行上面的代码后,将打印出如下的结果:

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    可以看出来,currentWindowStart每增加500ms,timeId就加1,这时就是时间窗口发生滑动的时候。

    总结

    介绍到这里,关于Sentinel的基本实现原理都讲了,具体怎么代码实现,小伙伴们可以去看源码调试看看。到了这里我们已经介绍了Sentinel很多相关的知识了。

    那是不是我们就可以用到生产环境呢?老顾告诉大家还少一个重要的东西,没了这个东西还是不能在生产环境应用自如,具体是什么东西呢?下篇文章老顾继续介绍。谢谢!!!

    你所不知道的Sentinel核心源码剖析,来吧!抓紧出坑!

    分享、在看与点赞都在这儿
    点下给小编加点料