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使用。

相关推荐
一只叫煤球的猫7 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9658 小时前
tcp/ip 中的多路复用
后端
bobz9658 小时前
tls ingress 简单记录
后端
皮皮林5519 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友9 小时前
什么是OpenSSL
后端·安全·程序员
bobz9659 小时前
mcp 直接操作浏览器
后端
前端小张同学12 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook12 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康13 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在13 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net