【Spring Cloud Alibaba】Sentinel运行原理

文章目录

  • 前言
  • 1、基本原理
  • 2、SphU.entry()
    • 2.1、StringResourceWrapper
    • 2.2、Entry
  • 3、entry.exit()
  • 4、Context

前言

本文基于sentinel-1.8.0版本

Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

sentinel整体设计的很精巧,只需要一个sentinel-core便可以运行,它提供了诸如服务降级、黑白名单校验、QPS、线程数、系统负载、CPU负载、流控等功能,可谓是功能非常的强大。

大家都知道sentinel使用SphU或者SphO标示一个被保护的资源,比如:

Entry entry = SphU.entry(“HelloWorld”, EntryType.IN);

上述代码标示了一个名为HelloWorld的被保护资源,并且检查入口流量(SystemSlot只对入口流量生效)。在这行代码之后,便可以访问被保护的资源了,那么SphU.entry()内部究竟做了什么?访问资源结束后,还要执行entry.exit(),那么entry.exit()又做了什么?本文接下来详细分析SphU.entry()和entry.exit()方法的执行原理。

1、基本原理

sentinel在内部创建了一个责任链,责任链是由一系列ProcessorSlot对象组成的,每个ProcessorSlot对象负责不同的功能,外部请求是否允许访问资源,需要通过责任链的校验,只有校验通过的,才可以访问资源,如果被校验失败,会抛出BlockException异常。

sentinel提供了8个ProcessorSlot的实现类,下面实现类功能介绍:

  1. DegradeSlot:用于服务降级,如果发现服务超时次数或者报错次数超过限制,DegradeSlot将禁止再次访问服务,等待一段时间后,DegradeSlot试探性的放过一个请求,然后根据该请求的处理情况,决定是否再次降级。
  2. AuthoritySlot:黑白名单校验,按照字符串匹配,如果在黑名单,则禁止访问。
  3. ClusterBuilderSlot:构建ClusterNode对象,该对象用于统计访问资源的QPS、线程数、异常、响应时间等,每个资源对应一个ClusterNode对象。
  4. SystemSlot:校验QPS、并发线程数、系统负载、CPU使用率、平均响应时间是否超过限制,使用滑动窗口算法统计上述这些数据。
  5. StatisticSlot:用于从多个维度(入口流量、调用者、当前被访问资源)统计响应时间、并发线程数、处理失败个数、处理成功个数等。
  6. FlowSlot:用于流控,可以根据QPS或者每秒并发线程数控制,当QPS或者并发线程数超过设定值,便会抛出FlowException异常。FlowSlot依赖于StatisticSlot的统计数据。
  7. NodeSelectorSlot:负责收集资源路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级、数据统计。
  8. LogSlot:打印日志。

比如本文开头的例子,当请求要访问HelloWorld资源时,该请求需要顺次经过上述这些slot的检查,同时当访问结束时StatisticSlot里面也记录下HelloWorld资源被访问的统计数据,当后面的请求再次访问该资源时,FlowSlot、DegradeSlot可以使用这些统计数据做检查。
sentinel使用SPI加载这些slot,并根据注解@SpiOrder的属性value对它们排序,value越小优先级越高。在sentinel中,这些slot的顺序是:
在这里插入图片描述

我们也可以添加自定义的slot,只需要实现ProcessorSlot接口,在com.alibaba.csp.sentinel.slotchain.ProcessorSlot文件中添加自定义类的全限定名,然后使用注解@SpiOrder指定顺序即可。
对于每个slot的实现原理在后面的文章做介绍。下面通过代码介绍一下SphU.entry()和entry.exit()内部都做了什么。

2、SphU.entry()

在介绍代码前先介绍两个对象。

2.1、StringResourceWrapper

entry()方法内部首先创建一个StringResourceWrapper对象,该对象表示被保护的资源,资源使用字符串命名,StringResourceWrapper对象有三个参数:

//资源名,也就是entry()方法的第一个入参
protected final String name;
//表示是入口流量(IN)还是出口流量(OUT),
//两个参数的区别在于是否被SystemSlot检查,IN会被检查,OUT不会,默认是OUT
protected final EntryType entryType;
//表示资源类型,sentinel提供了common、web、sql、api等类型,资源类型用于统计使用
protected final int resourceType;

任何一个被保护的资源都被封装成StringResourceWrapper对象,sentinel也是使用该对象识别被保护资源。

2.2、Entry

有了表示资源的对象后,接下来创建Entry对象,这个对象也是SphU.entry()方法的返回值,Entry对象持有资源对象,ProcessorSlot链,sentinel上下文对象Context,通过Entry对象应用程序可以窥探sentinel内部情况。

SphU.entry()通过一系列的调用最终调用到CtSph的entryWithPriority()方法上:

//resourceWrapper:是StringResourceWrapper对象,表示资源
//count:表示令牌数,默认是1,一般一个请求对应一个令牌,也可以指定一个请求对应多个令牌,如果令牌不够,则禁止访问
//prioritized:在FlowSlot里面使用,没找到具体的使用含义,有看懂的小伙伴可以告知一下
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)throws BlockException {//构建上下文对象,上下文对象存储在ThreadLocal中Context context = ContextUtil.getContext();if (context instanceof NullContext) {return new CtEntry(resourceWrapper, null, context);}//一般的线程第一次访问资源,context都是null,我们也可以在应用程序中使用ContextUtil自己创建Context对象if (context == null) {//下面创建了一个名字为sentinel_default_context的Context对象context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);}//全局开关,可以使用它来关闭sentinelif (!Constants.ON) {return new CtEntry(resourceWrapper, null, context);}//使用SPI构建slot链,每个slot对象都有一个next属性,可以使用该属性指定下一个slot对象ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);if (chain == null) {return new CtEntry(resourceWrapper, null, context);}//创建Entry对象Entry e = new CtEntry(resourceWrapper, chain, context);try {//对该请求,遍历每个slot对象chain.entry(context, resourceWrapper, null, count, prioritized, args);} catch (BlockException e1) {e.exit(count, args);throw e1;} catch (Throwable e1) {RecordLog.info("Sentinel unexpected exception", e1);}return e;
}

entryWithPriority()方法首先创建一个Context对象,这个对象将会贯穿整个请求的过程,一些共享数据可以放在这里面,既可以使用上面的代码创建名字为sentinel_default_context的Context对象,也可以在应用程序中创建Context对象,如果在应用程序中创建的话,上面代码就不会再次创建了:

//第一个参数表示Context名字,
//第二个参数表示请求方或者调用方的名字,当需要根据调用方进行控制的时候,第二个参数就会起作用
ContextUtil.enter("HelloWorld", "app");
Entry entry = SphU.entry("HelloWorld", EntryType.IN);

创建完Context对象后,使用SPI构建slot链,之后是创建Entry对象,之后就是遍历slot链以决定是否允许该请求访问资源。

3、entry.exit()

访问完资源后,需要调用entry.exit()以告知sentinel结束访问,sentinel会做一些资源的清理和数据统计工作。
entry.exit()方法最后调用到CtEntry.exitForContext()方法上:

 protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {if (context != null) {if (context instanceof NullContext) {return;}//如果Context对象记录的Entry对象不是当前对象,//意味着entry.exit()与SphU.entry()不是成对出现的,//sentinel要求两者必须成对出现,而且要一一对应,否则抛出异常//Context有父子关系,这个在文章后面介绍if (context.getCurEntry() != this) {String curEntryNameInContext = context.getCurEntry() == null ? null: context.getCurEntry().getResourceWrapper().getName();// Clean previous call stack.CtEntry e = (CtEntry) context.getCurEntry();while (e != null) {e.exit(count, args);e = (CtEntry) e.parent;}String errorMessage = String.format("The order of entry exit can't be paired with the order of entry"+ ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext,resourceWrapper.getName());throw new ErrorEntryFreeException(errorMessage);} else {//在遍历每个slot的exit方法,每个slot清理和统计数据if (chain != null) {chain.exit(context, resourceWrapper, count, args);}//遍历exitHandlers,相当于回调,一般的DegradeSlot有回调,//DegradeSlot根据服务访问状态,决定是否将降级状态由HALF_OPEN变为OPENcallExitHandlersAndCleanUp(context);//设置为上一级Context对象context.setCurEntry(parent);if (parent != null) {((CtEntry) parent).child = null;}if (parent == null) {// Default context (auto entered) will be exited automatically.if (ContextUtil.isDefaultContext(context)) {ContextUtil.exit();}}//设置当前对象的this.context = nullclearEntryContext();}}}

entry.exit()相对比较简单,它按照顺序再次遍历访问每个slot的exit()方法。

4、Context

Context是sentinel中的上下文对象,Context贯穿整个资源的访问过程。Context保存在ThreadLocal中。
创建Context有多种方式,可以像第二小节里面一样,创建一个默认的Context对象,也可以在访问资源前使用ContextUtil创建Context对象:

//name表示Context的名称或者链路入口的名称,origin表示调用来源的名称,默认为空字符串
public static Context enter(String name, String origin);
public static Context enter(String name);

无论是上面两种创建方式还是第二小节里面的创建方式,最终都是调用ContextUtil.trueEnter()方法:

protected static Context trueEnter(String name, String origin) {//contextHolder是ThreadLocal<Context>类型Context context = contextHolder.get();if (context == null) {//contextNameNodeMap持有系统所有的入口节点Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;DefaultNode node = localCacheNameMap.get(name);if (node == null) {//sentinel最大只能支撑2000个入口节点,如果超过2000个,sentinel无法提供对资源的保护if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {setNullContext();return NULL_CONTEXT;} else {LOCK.lock();try {node = contextNameNodeMap.get(name);if (node == null) {if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {setNullContext();return NULL_CONTEXT;} else {//创建入口节点node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);//入口节点作为虚拟根节点的子节点Constants.ROOT.addChild(node);Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);newMap.putAll(contextNameNodeMap);newMap.put(name, node);contextNameNodeMap = newMap;}}} finally {LOCK.unlock();}}}//创建Context对象,可以看到Context对象与入口节点一一对应context = new Context(node, name);//设置调用来源context.setOrigin(origin);contextHolder.set(context);}return context;}

Context对象持有名称和一个入口节点对象,入口节点与对应了线程访问的第一个资源,Context对象对应了线程对资源的一次访问,一个线程对应一个Context对象。而且每个入口节点对象都是虚拟根对象ROOT的子节点,虚拟根对象的定义如下:

//ROOT_ID=machine-root
public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON));

虚拟根对象的名字为machine-root。总的来说,Context是为了在访问资源的过程中保存共享数据使用的。
下面详细介绍一下sentinel中的访问链路树。
假如使用如下代码访问资源(来源官网):

  ContextUtil.enter("entrance1", "appA");Entry nodeA = SphU.entry("nodeA");if (nodeA != null) {nodeA.exit();}ContextUtil.exit();ContextUtil.enter("entrance2", "appA");nodeA = SphU.entry("nodeB");if (nodeA != null) {nodeA.exit();}ContextUtil.exit();

以上代码将在内存中生成以下结构:

               machine-root/         \/           \entrance1         entrance2    -------表示入口节点对象EntranceNode/               \/                 \DefaultNode(nodeA)   DefaultNode(nodeB)   ---------内部创建DefaultNode节点|               |          |               |ClusterNode(nodeA)   ClusterNode(nodeB)    ------------记录资源的访问数据

再看下面这个访问方式:

  ContextUtil.enter("entrance1", "appA");Entry nodeA = SphU.entry("nodeA");Entry nodeB = SphU.entry("nodeB");if (nodeB != null) {nodeB.exit();}if (nodeA != null) {nodeA.exit();}ContextUtil.exit();

上面这个代码创建的访问链路树如下:

              machine-root/          /            entrance1      -------表示入口节点对象EntranceNode/               /                 DefaultNode(nodeA)  ---------内部创建DefaultNode节点,持有一个ClusterNode对象/               /            DefaultNode(nodeB)  ------------记录资源的访问数据,持有一个ClusterNode对象    

每调用一次SphU.entry()方法都会在访问链路树上增加一个子节点,通过这个树可以还原出资源的访问路径。

每访问一个资源,Context对象都使用curEntry属性记录下正在访问资源对应的Entry对象,Entry对象有一个parent属性记录下父Entry,比如上面代码中,nodeB的父Entry是nodeA,Entry还有一个curNode属性,该属性记录了对应的DefaultNode对象。每个DefaultNode对象还有一个ClusterNode类的属性clusterNode,clusterNode的作用是记录被访问的资源的统计数据,比如平均响应时间、总请求数、QPS等,FlowSlot便是依据这些数据来判断是否允许访问资源。

Context可以通过上述这些属性构建出一个完整的资源访问树,并将资源访问数据更新到对应的ClusterNode对象中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/10252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ajax axios json

目录 一、ajax概述 1. 概念 2. 实现方式 &#xff08;1&#xff09;原生的JS实现方式&#xff08;了解&#xff09; &#xff08;2&#xff09; JQeury实现方式 二、axios 介绍 三、axios使用 1. axios 发送get/post请求 2. axios验证用户名称是否存在 四、json 1. …

设计模式——观察者模式

文章目录 1 概述2 实现3 总结 1 概述 观察者模式可以分为观察者和被观察者&#xff0c;观察者通过注册到一个被观察者中&#xff0c;也可视为订阅&#xff0c;当被观察者的数据发生改变时&#xff0c;会通知到观察者&#xff0c;观察者可以据此做出反应。 可以类比订阅报纸&am…

vue3+ts+elementui-plus二次封装弹框

一、弹框组件BaseDialog <template><div classmain><el-dialog v-model"visible" :title"title" :width"dialogWidth" :before-close"handleClose"><!-- 内容插槽 --><slot></slot><template…

【Docker】Consul的容器服务更新与发现

目录 一、Consul二、什么是服务注册与发现1.2什么是consul1.3consul提供的一些关键特性 二、Consul部署2.1环境配置2.2Consul服务器配置1. 建立 Consul 服务2. 查看集群信息3. 通过 http api 获取集群信息 2.3 registrator服务器配置1. 安装 Gliderlabs/Registrator2. 测试服务…

【数据挖掘】PCA/LDA/ICA:A成分分析算法比较

一、说明 在深入研究和比较算法之前&#xff0c;让我们独立回顾一下它们。请注意&#xff0c;本文的目的不是深入解释每种算法&#xff0c;而是比较它们的目标和结果。 如果您想了解更多关于PCA和ZCA之间的区别&#xff0c;请查看我之前基于numpy的帖子&#xff1a; PCA 美白与…

Leetcode-每日一题【114.二叉树展开为链表】

题目 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例…

JavaEE——Spring中存取Bean的注解

目录 一、存储Bean对象 1、定义 2、存储方式 &#xff08;1&#xff09;、类注解 【1】、Controller&#xff08;控制器存储&#xff09; 【2】、Service&#xff08;服务存储&#xff09; 【3】、Repository&#xff08;仓库存储&#xff09; 【4】、Component&#xf…

9个可用于图片转文本的最佳免费 OCR 软件

光学字符识别 (OCR) 软件可帮助将不可编辑的文档格式&#xff08;例如 PDF、图像或纸质文档&#xff09;转换为可编辑和可搜索的机器可读格式。 OCR 应用程序通常用于从 PDF 和图像中捕获文本&#xff0c;并将文本转换为可编辑格式&#xff0c;例如 Word、Excel 或纯文本文件。…

【LeetCode每日一题】——946.验证栈序列

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 栈 二【题目难度】 中等 三【题目编号】 946.验证栈序列 四【题目描述】 给定 pushed 和 p…

C++OpenCV(6):图像阈值操作

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图像阈值化 图像阈值化 阈值又叫临界值&#xff0c;是指一个效应能够产生的最低值或最高值。 例如我们选择的阈值为125&#xff0c;则…

智能网关实现混凝土搅拌机无人自动化

“以前的搅拌站生产时&#xff0c;是需要人工巡检的&#xff0c;运送物料和搅拌时产生的大量粉尘污染和噪音&#xff0c;让工人苦不堪言。但是如果有了物联网搅拌站监测系统智慧园区项目落地后&#xff0c;工人也不用去现场忍受噪音和粉尘了。” 行业痛点 传统模式下的混泥土…

机器学习实战11-基于K-means算法的文本聚类分析,生成文本聚类后的文件

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍机器学习实战11-基于K-means算法的文本聚类分析&#xff0c;生成文本聚类后的文件。文本聚类分析是NLP领域的一个核心任务&#xff0c;通过将相似的文本样本分组&#xff0c;可以帮助我们发现隐藏在文本数据中的模式和结…

力扣题库刷题笔记73--矩阵置零

1、题目如下&#xff1a; 2、个人Python代码实现 3、个人Python代码思路 a、声明2个空数组p、q&#xff0c;用于存放值为0的元素matrix[i][j]的下标 b、首先遍历二维数组matrix&#xff0c;找到值为0的元素matrix[i][j]&#xff0c;将下标i加入数组p&#xff0c;将下标j加入数…

高通WLAN框架学习(37)-- TDLS(Tunneled Direct Link Setup)通道直接链路建立

一 TDLS概述 隧道直连设置(TDLS)基于IEEE 802.11z-2010IEEE标准802.11z标准(无线局域网介质访问控制(MAC)和物理层(PHY)规范。 TDLS允许与同一AP关联的设备之间建立直接链路。Wi-Fi Direct允许设备之间直接连接,而不需要AP。Wi-Fi联盟认证可用于IEEE 802.11a和802.11g设备的T…

如何创建vue2,vue3项目

前提需安装node.js和Vue CLI node.js:https://nodejs.org/zh-cn Vue CLI&#xff1a; npm install -g vue/cli 如何创建一个vue2项目 &#xff08;1&#xff09; 使用cmd终端直接创建 进入到vue项目所创建的目录里&#xff08;我是直接创建在桌面上&#xff09; 选择vue2 …

入局元宇宙,所谓的无限可能到底在哪里?

最近的热点新闻表明&#xff0c;人们似乎认为元宇宙已经走向“死亡”。但实际上&#xff0c;市场应该重新定义对元宇宙的看法&#xff0c;以及正视它最大的机会所在——游戏领域。 1937年5月6日&#xff0c;一架名为兴登堡号的巨大氢能齐柏林飞艇飞临新泽西州曼彻斯特镇上空&a…

Jmeter+MySQL链接+JDBC Connection配置元件+使用

参考大大的博客学习&#xff1a;怎么用JMeter操作MySQL数据库&#xff1f;看完秒懂&#xff01;_jmeter mysql_程序员馨馨的博客-CSDN博客 注&#xff1a;里面所有没打码的都是假数据&#xff0c;麻烦大家自行修改正确的信息。 一、背景 需要取数据库中的值&#xff0c;作为…

Toyota Programming Contest 2023#4(AtCoder Beginner Contest 311)(A-G)

Contest Duration: 2023-07-22(Sat) 20:00 - 2023-07-22(Sat) 21:40 (local time) (100 minutes) 头文件和宏 #include<iostream> #include<string> #include<vector> using namespace std; #define int long long #define fer(i,a,b) for(int ia;i<b;i…

【运维】DevOps全流程笔记(未完成)

运维笔记 DevOps基本流程Code阶段工具&#xff08;gitlab安装&#xff09;Build阶段工具&#xff08;Maven安装&#xff09;Integrate阶段工具JenkinsJenkins介绍Jenkins安装Jenkins入门配置 CI/CD操作集成Sonar Qube集成HarborJenkins流水线Kubernetes编排工具 DevOps全流程笔…

怎么把PDF转为word?1分钟解决难题

PDF文件在我们的电脑上应用非常广泛&#xff0c;由于其较高的安全性和兼容性&#xff0c;得到了广泛的认可。然而&#xff0c;对于一些人来说&#xff0c;PDF文件不能直接进行编辑和修改可能是一个问题。因此&#xff0c;通常我们需要将其转换为Word格式&#xff0c;以便在Word…