Sentinel 源码学习

这篇具有很好参考价值的文章主要介绍了Sentinel 源码学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.7</version>
</dependency>

基本用法

try (Entry entry = SphU.entry("HelloWorld")) {
    // 被保护的逻辑
    System.out.println("hello world");
} catch (BlockException ex) {
    // 处理被流控的逻辑
    System.out.println("blocked!");
}

接下来,阅读源码,我们从SphU.entry()开始 

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

每个SphU#entry()将返回一个Entry。这个类维护了当前调用的一些信息:

  • createTime :这个entry的创建时间,用于响应时间统计
  • current Node :在当前上下文中的资源的统计
  • origin Node :原始节点的统计
  • ResourceWrapper :资源名称

CtSph#entryWithPriority()方法就是整个流控的基本流程:

1、首先,获取当前线程上下文,如果为空,则创建一个

2、然后,查找处理器链

3、最后,依次执行处理器

这是一个典型的责任链

接下来,挨个来看,首先看一下上下文。上下文是一个线程局部变量  ThreadLocal<Context>

Sentinel 源码学习

如果当前线程还没有上下文,则创建一个

Sentinel 源码学习

Sentinel 源码学习

有了Context之后,接下来查找处理器

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

这些功能插槽(slot chain)有不同的职责:

  • NodeSelectorSlot :负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
  • ClusterBuilderSlot :用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
  • StatisticSlot :用于记录、统计不同纬度的 runtime 指标监控信息;
  • FlowSlot :用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
  • AuthoritySlot :根据配置的黑白名单和调用来源信息,来做黑白名单控制;
  • DegradeSlot :通过统计信息以及预设的规则,来做熔断降级;
  • SystemSlot :通过系统的状态,例如 load1 等,来控制总的入口流量;

到这里为止,资源有了,上下文有了,处理器链有了,于是,接下来就可以对资源应用所有的处理器了

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

关于功能插槽的学习就先到这里,下面补充一个知识点:Node

Sentinel 源码学习

Node 用于保存资源的实时统计信息

StatisticNode 保存三种实时统计指标:

  1. 秒级指标
  2. 分钟级指标
  3. 线程数

DefaultNode 用于保存特定上下文中特定资源名称的统计信息

EntranceNode 代表调用树的入口

总之一句话,Node是用于保存统计信息的。那么,这些指标数据是如何计数的呢?

Sentinel 源码学习

Sentinel 源码学习

Sentinel 使用滑动窗口实时记录和统计资源指标。ArrayMetric背后的滑动窗口基础结构是LeapArray。

下面重点看一下StatisticNode

StatisticNode是用于实时统计的处理器插槽。在进入这个槽位时,需要分别计算以下信息:

  • ClusterNode :该资源ID的集群节点统计信息总和
  • Origin node :来自不同调用者/起源的集群节点的统计信息
  • DefaultNode :特定上下文中特定资源名称的统计信息
  • 最后,是所有入口的总和统计

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
    long timeId = timeMillis / windowLengthInMs;
    // Calculate current index so we can map the timestamp to the leap array.
    return (int)(timeId % array.length());
}

protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
    return timeMillis - timeMillis % windowLengthInMs;
}

/**
 * Get bucket item at provided timestamp.
 *
 * @param timeMillis a valid timestamp in milliseconds
 * @return current bucket item at provided timestamp if the time is valid; null if time is invalid
 */
public WindowWrap<T> currentWindow(long timeMillis) {
    if (timeMillis < 0) {
        return null;
    }

    int idx = calculateTimeIdx(timeMillis);
    // Calculate current bucket start time.
    long windowStart = calculateWindowStart(timeMillis);

    /*
     * Get bucket item at given time from the array.
     *
     * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
     * (2) Bucket is up-to-date, then just return the bucket.
     * (3) Bucket is deprecated, then reset current bucket.
     */
    while (true) {
        WindowWrap<T> old = array.get(idx);
        if (old == null) {
            /*
             *     B0       B1      B2    NULL      B4
             * ||_______|_______|_______|_______|_______||___
             * 200     400     600     800     1000    1200  timestamp
             *                             ^
             *                          time=888
             *            bucket is empty, so create new and update
             *
             * If the old bucket is absent, then we create a new bucket at {@code windowStart},
             * then try to update circular array via a CAS operation. Only one thread can
             * succeed to update, while other threads yield its time slice.
             */
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            if (array.compareAndSet(idx, null, window)) {
                // Successfully updated, return the created bucket.
                return window;
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
        } else if (windowStart == old.windowStart()) {
            /*
             *     B0       B1      B2     B3      B4
             * ||_______|_______|_______|_______|_______||___
             * 200     400     600     800     1000    1200  timestamp
             *                             ^
             *                          time=888
             *            startTime of Bucket 3: 800, so it's up-to-date
             *
             * If current {@code windowStart} is equal to the start timestamp of old bucket,
             * that means the time is within the bucket, so directly return the bucket.
             */
            return old;
        } else if (windowStart > old.windowStart()) {
            /*
             *   (old)
             *             B0       B1      B2    NULL      B4
             * |_______||_______|_______|_______|_______|_______||___
             * ...    1200     1400    1600    1800    2000    2200  timestamp
             *                              ^
             *                           time=1676
             *          startTime of Bucket 2: 400, deprecated, should be reset
             *
             * If the start timestamp of old bucket is behind provided time, that means
             * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
             * Note that the reset and clean-up operations are hard to be atomic,
             * so we need a update lock to guarantee the correctness of bucket update.
             *
             * The update lock is conditional (tiny scope) and will take effect only when
             * bucket is deprecated, so in most cases it won't lead to performance loss.
             */
            if (updateLock.tryLock()) {
                try {
                    // Successfully get the update lock, now we reset the bucket.
                    return resetWindowTo(old, windowStart);
                } finally {
                    updateLock.unlock();
                }
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
        } else if (windowStart < old.windowStart()) {
            // Should not go through here, as the provided time is already behind.
            return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
        }
    }
}

现在,有2个窗口,每个窗口500ms,2个窗口总共1000ms

假设,当前时间戳是1200ms,那么 (1200 / 500) % 2 = 0, 1200 - 1200 % 500 = 1000

这个时候,如果0这个位置没有窗口,则创建一个新的窗口,新窗口的窗口开始时间是1000ms

如果0这个位置有窗口,则继续判断旧窗口的窗口开始时间是否为1000ms,如果是,则表示窗口没有过期,直接返回该窗口。如果旧窗口的开始时间小于1000ms,则表示旧窗口过期了,于是重置旧窗口的统计数据,重新设置窗口开始时间(PS:相当于将窗口向后移动)

Sentinel 源码学习

Sentinel 源码学习

窗口(桶)数据保存在MetricBucket中

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

Sentinel 源码学习

总结一下:

1、每个线程过来之后,创建上下文,然后依次经过各个功能插槽

2、每个资源都有自己的处理器链,也就是说多次访问同一个资源时,用的同一套处理器链(插槽)

3、Node相当于是一个载体,用于保存资源的实时统计信息

4、第一次进入插槽后,创建一个新Node,后面再补充Node的信息;第二次进入的时候,由于上下文的名称都是一样的,所以不会再创建Node,而是用之前的Node,也就是还是在之前的基础上记录统计信息。可以这样理解,每个DefaultNode就对应一个特定的资源。

5、StatisticNode中保存三种类型的指标数据:每秒的指标数据,每分钟的指标数据,线程数。

6、指标数据统计采用滑动窗口,利用当前时间戳和窗口长度计算数据应该落在哪个窗口数组区间,通过窗口开始时间判断窗口是否过期。实际数据保存在MetricBucket中

最后,千言万语汇聚成这张原理图

Sentinel 源码学习

NodeSelectorSlot构造调用链路,ClusterBuilderSlot构造统计节点,StatisticSlot利用滑动窗口进行指标统计,然后是流量控制

 

参考文档

https://sentinelguard.io/zh-cn/docs/quick-start.html

https://sentinelguard.io/zh-cn/docs/basic-implementation.html

https://sentinelguard.io/zh-cn/docs/dashboard.html

https://blog.csdn.net/xiaolyuh123/article/details/107937353

https://www.cnblogs.com/magexi/p/13124870.html

https://www.cnblogs.com/mrxiaobai-wen/p/14212637.html

https://www.cnblogs.com/taromilk/p/11750962.html

https://www.cnblogs.com/taromilk/p/11751000.html

https://www.cnblogs.com/wekenyblog/p/17519276.html

https://javadoop.com/post/sentinel

https://www.cnblogs.com/cuzzz/p/17413429.html文章来源地址https://www.toymoban.com/news/detail-830550.html

到了这里,关于Sentinel 源码学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【VC++开发必备神器 -- Dependencies,查看依赖库DLL,支持win10,比depends更好用】

    软件之Dependencies,不同于depends 1、微软官方有提供depends,可以查看exe文件的依赖库,仅适用于winxp/win7/win8,但是不能用于win10,会卡死报错. 官网下载:Dependency Walker (depends.exe) Home Page 左上角第一个窗口是Dll信息窗口,显示程序所需的Dll模块。 如何使用Depends.exe查看dll动态链

    2024年02月05日
    浏览(47)
  • Angular 17+ 高级教程 – Dependency Injection 依赖注入

    本来是想先介绍 Angular Component 的,但 Component 里面会涉及到一些 Dependency Injection (简称 DI) 的概念,所以还是先介绍 DI 吧。 温馨提醒:如果你对 JS class、prototype 不太熟悉的话,建议你先看这篇 JavaScript – 理解 Object, Class, This, Prototype, Function, Mixins   首先我们有一个 class Ser

    2024年03月09日
    浏览(61)
  • IDEA库(Libraries)和模块依赖(Dependencies)详解

    库是模块可以依赖的已编译代码的集合。在IntelliJ IDEA中,可以在三个级别上定义库: 全局 (可用于许多项目), 项目 (可用于 项目 中的所有模块)和 模块 (可用于一个模块) 简单来说,IDEA中的库(Libraries)就是用来存放外部jar包,我们的项目或模块需要某些jar包时,

    2024年01月25日
    浏览(40)
  • sentinel引入CommonFilter类

    最近在做一个springcloudAlibaba项目,做链路流控模式时需要将入口资源关闭聚合,做法如下: spring-cloud-alibaba v2.1.1.RELEASE及前,sentinel1.7.0及后: 1.pom 中引入: 2.添加一下配置: spring-cloud-alibaba v2.1.1.RELEASE后: 配置文件添加: spring.cloud.sentinel.web-context-unify=false

    2024年02月14日
    浏览(24)
  • depends_on 解决 docker 容器依赖问题

    如果你经常使用 docker-compose 启动服务的话,可能会遇到下面的问题: 服务 B 依赖服务 A,需要服务 A 先启动,再启动服务 B 举个例子,在部署 kafka 集群的时候,需要启动两个kafka,并使用zookeeper做注册中心,docker-compose.yaml 文件如下 此时会同时启动 3 个容器,zookeeper、kafka

    2024年02月08日
    浏览(40)
  • Go 开源库运行时依赖注入框架 Dependency injection

    一个Go编程语言的运行依赖注入库。依赖注入是更广泛的控制反转技术的一种形式。它用于增加程序的模块化并使其具有可扩展性。 依赖注入是更广泛的控制反转技术的一种形式。它用于增加程序的模块化并使其具有可扩展性。 Providing Extraction Invocation Lazy-loading Interfaces Gro

    2024年02月07日
    浏览(51)
  • PostgreSQL-视图-03-查询对象依赖关系视图-dba_dependencies

    PostgreSQL查询对象依赖关系视图

    2024年02月15日
    浏览(45)
  • 测试框架pytest教程(2)-用例依赖库-pytest-dependency

    对于 pytest 的用例依赖管理,可以使用  pytest-dependency  插件。该插件提供了更多的依赖管理功能,使你能够更灵活地定义和控制测试用例之间的依赖关系。 Using pytest-dependency — pytest-dependency 0.5.1 documentation 安装 pytest-dependency 插件: 依赖方法和被依赖方法都需要使用装饰器

    2024年02月11日
    浏览(35)
  • Sentinel如何实现对分布式系统的高可用性和流量控制?我们通过源码一起学习

    前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章将详细介绍Sentinel源码实现对分布式系统高可用性和流量控制,后续文章将详细介绍Sentinel的其他知识。 如果文章有什么需要改进的地方还请大佬不吝赐教 👏👏。 小威在此先感谢各位大佬啦~~🤞🤞

    2024年02月06日
    浏览(42)
  • Maven项目中的依赖出现版本冲突,最终发现是对Dependency Scope理解有误

    再来个文章目录 本文记录一下遇到maven依赖版本冲突后的排查过程说明以及问题原因说明 下面还有投票,帮忙投个票👍 最近加入了 Apache Dubbo 开源社区,成为了一名Dubbo Contributor。在熟悉Dubbo中的各个RPC协议时根据官网提供的示例搭建了一个示例。在熟悉过后想看下谷歌提供

    2023年04月09日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包