Sentinel底层实现(一):sentinel的关键类

前言

笔记仅用来记录sentinel限流降级的底层逻辑,不涉及如何整合spring cloud知识,所以只针对sentinel-core包内的源码进行讲解。你需要提前了解如下知识:

  1. 多线程下的线程安全问题
  2. 什么是ThreadLocal
  3. 责任链设计模式
  4. 什么是滑动窗口算法
  5. sentinel底层实现的官方文档

环境准备

  1. 创建一个maven项目
  2. 引入sentinel的核心依赖:
xml 复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version>
</dependency>
  1. 创建一个测试类SentinelTest:
    1. 在initFlowRules()中定义了限流规则:针对app_A的调用,如果QPS>20则限流
    2. 模拟两个应用(app_A、app_B)通过HTTP的方式并发调用/sentinelTest/user/info。
    3. 调用成功则日志打印pass,否则打印block
java 复制代码
package sentinel;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class SentinelTest {
    private static final String USER_INFO_API = "/sentinelTest/user/info";
    private static final String APP_A = "app_A";
    private static final String APP_B = "app_B";

    private static final Executor EXECUTOR_FOR_HTTP = new ThreadPoolExecutor(10, 20, 1000, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(180));
    private static final String INVOKE_TYPE_HTTP = "HTTP";

    private static final Executor EXECUTOR_FOR_RPC = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(100));
    private static final String INVOKE_TYPE_RPC = "RPC";

    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setLimitApp(APP_A);
        rule.setResource(USER_INFO_API);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(20);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

    public static void main(String[] args) {
        // 配置规则.
        initFlowRules();
        int i = 0;

        AtomicInteger COUNTER_FRO_APP_A = new AtomicInteger(0);
        AtomicInteger COUNTER_FRO_APP_B = new AtomicInteger(0);
        while (i++ < 100) {
            EXECUTOR_FOR_HTTP.execute(() -> userInfo(COUNTER_FRO_APP_A, INVOKE_TYPE_HTTP, APP_A));
            EXECUTOR_FOR_HTTP.execute(() -> userInfo(COUNTER_FRO_APP_B, INVOKE_TYPE_HTTP, APP_B));
        }
    }

    public static String userInfo(AtomicInteger counter, String contextName, String origin) {
        ContextUtil.enter(contextName, origin);
        try (Entry entry = SphU.entry(USER_INFO_API)) {
            // 被保护的逻辑
            System.out.printf("请求次数[%s],应用[%s]通过[%s]调用资源[%s],pass%n", counter.addAndGet(1), origin, contextName, USER_INFO_API);
        } catch (BlockException ex) {
            // 处理被流控的逻辑
            System.out.printf("请求次数[%s],应用[%s]通过[%s]调用资源[%s],block%n", counter.addAndGet(1), origin, contextName, USER_INFO_API);
        }
        ContextUtil.exit();
        return "user";
    }
}
  1. 执行结果
  2. 结果解析
    1. 应用app_A在第21次调用时,被block了,说明限流规则生效了,执行结果:符合预期
    2. 应用app_B在第40次调用时,依然能pass,说明没被限流,执行结果:符合预取

sentinel关键类

度量指标

LongAdder

名称:Long类型数据的加减器 作用:能线程安全地对Long类型数据进行加减操作,是各个指标的基础存储单元。 描述:比如记录某一资源的访问成功次数success,底层就是LongAdder类型的。LongAdder.class通过继承juc包下的Striped64.class,能线程安全地对Long类型数据进行加减操作,因此,当并发访问某一接口时,能保证统计数据的准确性。

MetricBucket

名称:度量桶、指标桶 作用:统计资源在一段时间内运行数据 描述:比如统计1000ms内某一资源的访问的通过次数、阻塞次数、成功次数、异常次数、资源的RT,数据就存储在一个MetricBucket对象中。MetricBucket类通过维护内部一个LongAdder数组,来记录各个指标在一段时间内的数量,定义的指标请看MetricEvent。

LeapArray<?>

名称:LeapArray 作用:可以使用滑动窗口算法对数据进行计数。 描述:LeapArray是一个抽象类,定义了sentinel中的基础统计指标。通过定义窗口总长度(ms)、间隔长度(ms)、采样数量,内部实现了滑动窗口算法的逻辑 实现类:

  1. BucketLeapArray
  2. OccupiableBucketLeapArray
  3. SimpleErrorCounterLeapArray
  4. SlowRequestLeapArray
  5. .......

Metric

名称:度量、指标 作用:定义了一个资源的运行指标,包括:成功数量、报错数量、通过数量、阻塞数量...... 描述:Metic是一个接口,定义了资源的调用指标。它的实现类ArrayMetric内部持有LeapArray对象,可以理解为LeapArray进一步封装,可以很简单地获取资源的当前运行指标,如:成功数量、报错数量、通过数量、阻塞数量等........ 实现类:ArrayMetric

类之间地关系图

ArrayMetric内部持有一个BucketLeapArray对象,BucketLeapArray内部持有一个MetricBucket数组,而MetricBucket持有一个LongAdder数组。总的来说,它们之间的关系如图所示:

举个例子,来理解这个数据结构:以200ms作为间隔,统计接口A在1s内请求的pass数、block数、exception数。那么接口A会对应一个ArrayMetric对象,也就是对应一个BucketLeapArray对象;一个BucketLeapArray对应5个MetricBucket对象,每个MetricBucket记录接口A在200ms内的运行数据;一个MetricBucket对应3个LongAdder对象,它们分别pass数、block数、exception数。

资源节点

Node

名称:资源节点 作用:用于记录和存储资源的实时统计数据 描述:Node接口定义一个资源的实时运行数据,比如pass数、block数、exception数等。什么是资源呢?在后端服务中,一个/user/info接口、一个facade接口都可以是资源,一个Node对象可以理解为一个后端接口在一段时间内的运行状态。 实现类:

  1. StatisticNode
    1. DefaultNode
      1. EntranceNode
    2. ClusterNode

StatisticNode实现了Node接口,具备了统计能力,所谓统计能力是指StatisticNode对象提供了方法,可以轻松的修改、读取资源的运行数据,方便判断资源是否过载、是否需要限流。DefaultNode、EntranceNode、ClusterNode都继承自StatisticNode,所以它们都具备统计能力。之所以要区分不同类型的Node,是因为这样可以从不同粒度统计资源的运行状态,看完下面的类之间的关系图,就很好理解资源和各种Node之间的关系了。

类之间的关系图

如何理解资源和各种Node之间的关系呢,举个例子,服务中有个UserManager提供了查询用户信息的queryUser()方法,对外分别提供了http接口以及rpc接口,当然我们可以将这两个接口定义成两个资源,但实际底层都是调用的queryUser()方法,所以定义成一个资源可能更恰当,这个时候如何区分来源是http和rpc呢?可以在http接口中调用 ContextUtil.enter("http", origin),在rpc接口调用 ContextUtil.enter("rpc", origin),在queryUser()方法中调用SphU.entry("queryUser"),这时候在sentinel内部,资源和各种Node之间的关系是:

Root_Node可以理解为是特殊的Node对象,一个应用只有一个Root_Node。由于我们分别调用ContextUtil.enter("http", origin)、ContextUtil.enter("rpc", origin)来区分不同来源,所以在Root_Node下面存在两个EntranceNode对象分别表示http和rpc调用方式,这两个EntranceNode对象分别指向两个不同的DefualtNode对象,它们的名字都是queryUser,分别记录queryUser()在http和rpc调用方式的实时运行数据,而这两个同名的DefaultNode都会指向一个名为queryUser的ClusterNode对象,ClusterNode对象记录了queryUser总的运行数据。这样组织Node对象,可以做到从不同粒度对queryNode()做限流降级,比如我可以只针对rpc调用方式的queryUser()做限流,也可以针对所有querUser()的调用做限流。 进一步来说,假设UserManger还存在一个queryDepatment()方法,对外只提供了http接口,那这个时候Node在sentinel中的组织方式是:

在介绍资源与各种node的关系时,我们提到StatisticNode具有统计数据的能力,但没解释它是怎么实现数据统计的,这里涉及到Node和Metric的关系。在指标度量中已经介绍了ArrayMetric(Metric的子类)能线程安全地记录一段时间内的运行数据,StatisticNode对象内部包就含两个ArrayMetric对象分别用来记录资源在1秒和1分钟内地运行数据。

处理器插槽

ProcessorSlot

名称:处理器插槽 描述:定义了sentinel实现限流和降级的底层接口,各实现类负责特定的功能模块,比如NodeSelectorSlot负责构建DefaultNode的映射关系,并根据contextname获取对应的DefaultNode、ClusterBuilderSlot负责构建ClusterNode的映射关系、StatisticSlot负责数据统计...... 实现类:

  1. AbstractLinkedProcessorSlot
    1. NodeSelectorSlot
    2. ClusterBuilderSlot
    3. LogSlot
    4. StatisticSlot
    5. AuthoritySlot
    6. SystemSlot
    7. FlowSlot
    8. DegradeSlot
    9. ProcessorSlotChain
      1. DefaultProcessorSlotChain

ProcessorSlotChain比较特殊,它虽然继承自AbstractLinkedProcessorSlot,但它并不像NodeSelectorSlot一样负责特定的模块功能,而仅仅定义了两个接口方法addFirst、addLast,让子类DefaultProcessorSlotChain实现如何在处理器插槽链的前后面增加处理器插槽,也就是说DefaultProcessorSlotChain的作用是按顺序将ProcessorSlot的实现类按链表的方式组装,DefaultProcessorSlotChain内部记录链表的first和last节点。

在sentinel中,一个资源只对应一个DefaultProcessorSlotChain对象,还是以UserManager为例,我们把它的queryUser()和queryDepatment()定义成资源,那么它们的映射关系是:

规则及其管理器

Rule

作用:存储降级或限流规则,还具备根据Node的运行数据判断是否需要限流或降级 实现类:

  1. AbstractRule
    1. FlowRule
    2. DegradeRule
    3. SystemRule
    4. AuthorityRul

FlowRuleManager

名称:限流规则管理器 描述:负责载入和读取限流规则,FlowSlot内部将用到。

DegradeRuleManager

名称:降价规则管理器 描述:负责载入和读取降级规则,DegredeSlow内部将用到。

其他

Context

名称:表示上下文 描述:存在一个ThreadLocal contextHolder,每个线程都会有自己的一个Context对象,如果没有就会创建,创建时会将contextName、EntranceNode对象、origin等信息保存起来,为后续ProcessorSlotChain使用。

相关推荐
学java的小菜鸟啊15 分钟前
第五章 网络编程 TCP/UDP/Socket
java·开发语言·网络·数据结构·网络协议·tcp/ip·udp
zheeez19 分钟前
微服务注册中⼼2
java·微服务·nacos·架构
程序员-珍23 分钟前
SpringBoot v2.6.13 整合 swagger
java·spring boot·后端
徐*红30 分钟前
springboot使用minio(8.5.11)
java·spring boot·spring
聆听HJ31 分钟前
java 解析excel
java·开发语言·excel
海里真的有鱼31 分钟前
好文推荐-架构
后端
AntDreamer35 分钟前
在实际开发中,如何根据项目需求调整 RecyclerView 的缓存策略?
android·java·缓存·面试·性能优化·kotlin
java_heartLake39 分钟前
设计模式之建造者模式
java·设计模式·建造者模式
G皮T39 分钟前
【设计模式】创建型模式(四):建造者模式
java·设计模式·编程·建造者模式·builder·建造者
niceffking44 分钟前
JVM HotSpot 虚拟机: 对象的创建, 内存布局和访问定位
java·jvm