引入依赖

<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()开始

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

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

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

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

2、然后,查找处理器链

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

这是一个典型的责任链

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

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

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

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

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

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

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

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

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

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

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

EntranceNode 代表调用树的入口

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

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

下面重点看一下StatisticNode

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

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

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:相当于将窗口向后移动)

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

总结一下:

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

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

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

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

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

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

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

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

Sentinel 源码学习的更多相关文章

  1. 5.Sentinel源码分析—Sentinel如何实现自适应限流?

    Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...

  2. 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?

    Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...

  3. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  4. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  9. 我的angularjs源码学习之旅2——依赖注入

    依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...

  10. ddms(基于 Express 的表单管理系统)源码学习

    ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...

随机推荐

  1. SV Clocking Review

    clocking会设置input和output的延时 default input #3ns output #1ns 数据是在时钟上升沿驱动的,在时钟上升沿,将vld驱动到dut,dut中也会在时钟上升 ...

  2. JMS微服务开发示例(八)双机热备

    双机热备,指两个一模一样的微服务,两个同时在运行,但是只有一个在工作,当工作中的微服务垮掉后,另一个会自行补上. 要实现这个,只需要设置 SingletonService = true. var mi ...

  3. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.17)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  4. 44从零开始用Rust编写nginx,命令行参数的设计与解析及说明

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...

  5. [转帖]Run Grafana behind a reverse proxy

    On this page Introduction Configure NGINX Configure HAProxy Configure IIS Configure Traefik Summary ...

  6. [转帖]global cache cr request等待事件分析及优化

    在RAC环境中,和全局调整缓存相关的最常见的等待事件无非就是:global cache cr request,global cache busy和equeue 在XX电信做了一次数据库巡检中发现,sp ...

  7. [转帖]Linux fuse用户态文件系统及其libfuse

    https://www.jianshu.com/p/abc5524ac18c 为什么要有用户态文件系统 VFS文件系统可知文件系统在内核态的,应用程序操作文件,统一调用内核态的VFS层抽象接口. 突然 ...

  8. [转帖]HTTP状态码、请求方法、响应头信息

    https://www.cnblogs.com/pachongshangdexuebi/p/5279608.html HTTP状态码 当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求. ...

  9. 【转帖】MySQL索引

    数据表如何用索引快速查找 索引是 排好序的快速查找的数据结构 索引存储在文件系统中 索引的文件存储形式与存储引擎有关 索引数据结构:可以是二叉树.红黑树.Hash表.B-Tree.B+Tree 1.二 ...

  10. 【转帖】《MySQL高级篇》四、索引的存储结构

    1. 为什么使用索引 假如给数据使用 二叉树 这样的数据结构进行存储,如下图所示 2.索引及其优缺点 2.1 索引概述 2.2 优点 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的 IO ...