【Sentinel】Sentinel簇点链路的形成

说明

一切节点的跟是 machine-root,同一个资源在不同链路会创建多个DefaultNode,但是在全局只会创建一个 ClusterNode

                machine-root/\/    \EntranceNode1   EntranceNode2/          \/              \DefaultNode(nodeA)         DefaultNode(nodeA)|                  |- - - - - - + - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);

如我们所见,在两个上下文中为“nodeA”创建了两个 DefaultNode,但只创建了一个 ClusterNode

一切的开始

DispatcherServlet是Spring MVC框架中的核心组件,它作为前置控制器,它拦截匹配的请求,并根据相应的规则分发到目标Controller来处理。当请求进入后,首先会执行DispatcherServlet 的 doDispatch 方法

public class DispatcherServlet extends FrameworkServlet {protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {···try {try {···// 执行preHandle方法 // 会进入AbstractSentinelInterceptor 的 preHandle// 会为当前访问的controller接口创建资源if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 最终会执行SentinelResourceAspect#invokeResourceWithSentinel(pjp);// 为所有添加注解的方法创建资源mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}···}catch (Exception ex) {···}}catch (Exception ex) {···}}
}

因此,从这里就可以知道,簇点链路中,默认使用 controller 创建的资源一定在使用注解创建的资源之前创建,也就是说,使用注解创建的资源只能作为使用 controller 创建的资源的子节点。

链路创建过程分析

创建 EntranceNode

上面说到,执行会进入AbstractSentinelInterceptor 的 preHandle,进行资源创建

public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {try {// 获取资源名,也就是 /order/{orderId}String resourceName = getResourceName(request);if (StringUtil.isEmpty(resourceName)) {return true;}if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {return true;}// Parse the request origin using registered origin parser.String origin = parseOrigin(request);// contextName默认是sentinel_spring_web_context,// 如果不想使用这个,而是使用Controller接口路径作为contextName,则需要在application.yml文件中关闭context整合// spring.cloud.sentinel.web-context-unify=falseString contextName = getContextName(request);// 创建context// Context初始化的过程中,会创建EntranceNode,contextName就是EntranceNode的名称ContextUtil.enter(contextName, origin);// 创建资源,簇点链路的形成就在里面Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);return true;} catch (BlockException e) {···}}
}

在获取 contextName 时,会先判断有没有关闭 context 整合,然后选择返回默认的sentinel_spring_web_contex还是从接口中获取url

@Override
protected String getContextName(HttpServletRequest request) {if (config.isWebContextUnify()) {return super.getContextName(request);}return getResourceName(request);
}@Override
protected String getResourceName(HttpServletRequest request) {// Resolve the Spring Web URL pattern from the request attribute.Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);if (resourceNameObject == null || !(resourceNameObject instanceof String)) {return null;}String resourceName = (String) resourceNameObject;UrlCleaner urlCleaner = config.getUrlCleaner();if (urlCleaner != null) {resourceName = urlCleaner.clean(resourceName);}// Add method specification if necessaryif (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) {resourceName = request.getMethod().toUpperCase() + ":" + resourceName;}return resourceName;
}

然后会进行 context 的创建

protected static Context trueEnter(String name, String origin) {// 第一次肯定为空Context context = contextHolder.get();if (context == null) {// contextNameNodeMap 有1个值(EntranceNode是DefaultNode的子类,是一种特殊的DefaultNode)//      1. sentinel_default_context -> {EntranceNode@10330} Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;// 根据传入的contextName选择看有没有这个name的EntranceNodeDefaultNode node = localCacheNameMap.get(name);// 如果没有就创建一个if (node == null) {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 {// 创建一个新的EntranceNodenode = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);// Add entrance node.// Constants.ROOT是一个EntranceNode, id是machine-root// 将当前创建的EntranceNode添加为Constants.ROOT的子节点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();}}}// 创建一个新的contextcontext = new Context(node, name);context.setOrigin(origin);contextHolder.set(context);}return context;
}

创建 DefaultNode

创建资源时,首先会创建一个 slot 执行链,然后依次执行。

第一个节点是 NodeSelectSlot,在里面完成 DefaultNode 的创建。

当第一次访问时,NodeSelectorSlot 中

// volatile保证map多线程的可见性
// 非static变量,每次创建对象时都创建一个新的
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable {// 第一次一定是空,同一个链路中的资源之后的请求不为空// 不同链路中的资源,后续的请求中,第一次访问还是空DefaultNode node = map.get(context.getName());if (node == null) {synchronized (this) {node = map.get(context.getName());if (node == null) {// 创建一个DefaultNode,将他放入到map中node = new DefaultNode(resourceWrapper, null);HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());cacheMap.putAll(map);cacheMap.put(context.getName(), node);// 更新mapmap = cacheMap;// Build invocation tree// 将刚创建的node设置为当前node的子节点((DefaultNode) context.getLastNode()).addChild(node);}}}// 设置当前节点为刚创建的节点context.setCurNode(node);fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

下面的图是访问/order/query/{name}接口创建的资源

下面的图是访问/order/query/{name}接口创建的资源,不过在这个 Controller 接口里面又调用了 service 中添加了@SentinelResource注解的方法。根据上面的分析,基于注解的资源后创建,因此它作为基于 Controller 创建的资源的子节点

第二次访问/order/query/{name}接口,在NodeSelectorSlot中,node 会获取到,因此直接执行后边的操作

如果 controller 接口上加了@SentinelResource,还是先创建 controller 资源,然后创建 controller 基于注解的资源,然后是 service 的资源。下面的图中,在/order/query/{name}Controller 接口上添加了@SentinelResource注解。

feign 对 Sentinel 支持

开启 feign 对 Sentinel 的支持后,Sentinel 会将 feign 的请求添加到簇点链路中

feign.sentinel.enabled=true

在 Sentinel 的 jar 中,使用 spi 机制加载了一个类com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration

SentinelFeignAutoConfiguration 配置类里定义了Feign.Builder 的实现类 SentinelFeign.builder()

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBean@ConditionalOnProperty(name = "feign.sentinel.enabled") // 配置项为true时该bean生效public Feign.Builder feignSentinelBuilder() {return SentinelFeign.builder();}}

SentinelFeign.builder( )build( ) 方法

主要作用是: 创建 invocationHandlerFactory,重写create( ) 方法;invocationHandlerFactory 用于创建 SentinelInvocationHandler ,代替前面的 FeignCircuitBreakerInvocationHandler。

public Feign build() {super.invocationHandlerFactory(new InvocationHandlerFactory() {public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {GenericApplicationContext gctx = (GenericApplicationContext)Builder.this.applicationContext;BeanDefinition def = gctx.getBeanDefinition(target.type().getName());FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean)def.getAttribute("feignClientsRegistrarFactoryBean");// 从BeanDefinition 里获取到 fallback、fallbackFactory Class fallback = feignClientFactoryBean.getFallback();Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();String beanName = feignClientFactoryBean.getContextId();if (!StringUtils.hasText(beanName)) {beanName = feignClientFactoryBean.getName();}if (Void.TYPE != fallback) {// 创建 fallback 实例Object fallbackInstance = this.getFromContext(beanName, "fallback", fallback, target.type());// 创建 SentinelInvocationHandlerreturn new SentinelInvocationHandler(target, dispatch, new org.springframework.cloud.openfeign.FallbackFactory.Default(fallbackInstance));} else if (Void.TYPE != fallbackFactory) {FallbackFactory fallbackFactoryInstance = (FallbackFactory)this.getFromContext(beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class);return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);} else {return new SentinelInvocationHandler(target, dispatch);}}private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {Object fallbackInstance = Builder.this.feignContext.getInstance(name, fallbackType);if (fallbackInstance == null) {throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));} else if (!targetType.isAssignableFrom(fallbackType)) {throw new IllegalStateException(String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", type, fallbackType, targetType, name));} else {return fallbackInstance;}}});super.contract(new SentinelContractHolder(this.contract));return super.build();
}

在 invoke 方法里面为feign请求创建资源创建资源

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler = args.length > 0 && args[0] != null? Proxy.getInvocationHandler(args[0]): null;return equals(otherHandler);}catch (IllegalArgumentException e) {return false;}}else if ("hashCode".equals(method.getName())) {return hashCode();}else if ("toString".equals(method.getName())) {return toString();}Object result;MethodHandler methodHandler = this.dispatch.get(method);// only handle by HardCodedTargetif (target instanceof Target.HardCodedTarget) {Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP.get(hardCodedTarget.type().getName()+ Feign.configKey(hardCodedTarget.type(), method));// resource default is HttpMethod:protocol://urlif (methodMetadata == null) {result = methodHandler.invoke(args);}else {String resourceName = methodMetadata.template().method().toUpperCase()+ ":" + hardCodedTarget.url() + methodMetadata.template().path();Entry entry = null;try {ContextUtil.enter(resourceName);// 为feign请求创建资源entry = SphU.entry(resourceName, EntryType.OUT, 1, args);// 调用服务端接口result = methodHandler.invoke(args);}catch (Throwable ex) {// fallback handleif (!BlockException.isBlockException(ex)) {Tracer.trace(ex);}if (fallbackFactory != null) {try {//异常时 调用熔断逻辑Object fallbackResult = fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args);return fallbackResult;}catch (IllegalAccessException e) {····}}else {···}}finally {···}}}else {result = methodHandler.invoke(args);}return result;
}

如果 service 中使用 feign,则 feign 的调用 也会现实在链路中,他和使用注解创建的service 资源是同级的,但是先创建 feign,后创建 service 注解资源

使用注解和 feign 创建的资源,EntryType 都是 OUT,只有 controller 资源的EntryType 是 IN。

EntryType:枚举标记资源调用方向。

创建ClusterNode

在创建 ClusterNode 时,使用 static 变量存储。将创建的 ClusterNode 与当前 node 进行关联。

/*** 请记住,相同的资源(ResourceWrapper.equals(Object))将在全局范围内共享相同的ProcessorSlotChain,而与上下文无关。* 因此,如果代码进入entry(Context,ResourceWrapper,DefaultNode,int,boolean,Object...),* 则资源名称必须相同,但上下文名称可能不同。要获得不同上下文中相同资源的总统计数据,* 相同的资源在全局范围内共享相同的ClusterNode。此映射在应用运行时间越长,就会变得越稳定。* 因此,我们不使用并发映射,而是使用锁。因为此锁仅在开始时发生,而并发映射将始终保持锁定状态。*/
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,boolean prioritized, Object... args) throws Throwable {// 如果不是第一次访问这个资源,则clusterNode是一定有的// 所以直接将DefaultNode和ClusterNode进行关联// 因为保存ClusterNode的map是static 的,因此全局共享,且创建后内容一直存在// 因此一个资源只会创建一次ClusterNodeif (clusterNode == null) {synchronized (lock) {if (clusterNode == null) {// Create the cluster node.clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));newMap.putAll(clusterNodeMap);newMap.put(node.getId(), clusterNode);clusterNodeMap = newMap;}}}node.setClusterNode(clusterNode);/** if context origin is set, we should get or create a new {@link Node} of* the specific origin.*/if (!"".equals(context.getOrigin())) {Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());context.getCurEntry().setOriginNode(originNode);}fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

说明

如果一个请求中要经过多个资源保护的方法(controller 资源*1,注解资源*n),则上面的流程会进行多次,分别根据资源创建类型执行对应的方法,从而将每次的资源添加到前面资源的字节的中,形成于给完整的簇点链路

后面就是限流的一些 slot

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

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

相关文章

QGIS003:【04地图导航工具栏】-地图显示、新建视图、时态控制、空间书签操作

摘要&#xff1a;QGIS地图导航工具栏包括平移地图、居中显示、放大、缩小、全图显示、缩放到选中要素、缩放到图层、缩放到原始分辨率、上一视图、下一视图、新建地图视图、新建3D地图视图、新建空间书签、打开空间书签、时态控制面板、刷新等选项&#xff0c;本文介绍各选项的…

Zynq UltraScale+ XCZU9EG 纯VHDL解码 IMX214 MIPI 视频,2路视频拼接输出,提供vivado工程源码和技术支持

目录 1、前言免责声明 2、我这里已有的 MIPI 编解码方案3、本 MIPI CSI2 模块性能及其优越性4、详细设计方案设计原理框图IMX214 摄像头及其配置D-PHY 模块CSI-2-RX 模块Bayer转RGB模块伽马矫正模块VDMA图像缓存Video Scaler 图像缓存DP 输出 5、vivado工程详解PL端FPGA硬件设计…

智慧公厕:打造更美好的城市生活环境

在信息技术迅猛发展的今天&#xff0c;智慧公厕作为一种创新的城市管理模式&#xff0c;正逐渐受到人们的关注。以物联网、大数据、人工智能为基础&#xff0c;智慧公厕正逐步改变传统公厕的面貌&#xff0c;为城市居民提供更便捷、舒适的公共服务。本文将以智慧公厕源头厂家广…

数据结构初阶——时间复杂度

朋友们我们又见面了&#xff0c;今天我们来学习数据结构的时间复杂度&#xff0c;在讲数据结构之前&#xff0c;大家可能只知道我们学习的是数据结构&#xff0c;但是还是不知道数据结构的具体定义&#xff0c;其实就是在内存上的数据。然后我们就像通讯录一样对它进行增删查改…

SQL server中:常见问题汇总(如:修改表时不允许修改表结构、将截断字符串或二进制数据等)

SQL server中&#xff1a;常见问题汇总 1.修改表时提示&#xff1a;不允许修改表结构步骤图例注意 2.将截断字符串或二进制数据。3.在将 varchar 值 null 转换成数据类型 int 时失败。4.插入insert 、更新update、删除drop数据失败&#xff0c;主外键FOREIGN KEY 冲突5.列不允许…

SpringAOP源码解析之advice构建排序(二)

上一章我们知道Spring开启AOP之后会注册AnnotationAwareAspectJAutoProxyCreator类的定义信息&#xff0c;所以在属性注入之后initializeBean的applyBeanPostProcessorsAfterInitialization方法执行的时候调用AnnotationAwareAspectJAutoProxyCreator父类(AbstractAutoProxyCre…

三步,金蝶K3的数据可视化了

数据可视化的一大特点就是“一图胜千言”&#xff0c;没什么能比图表更直观展现数据的了。那&#xff0c;金蝶K3系统上那海量数据能不能也做成数据可视化报表&#xff1f;操作复杂吗&#xff0c;难度大吗&#xff1f; 换了别的软件来做&#xff0c;操作多、难度大是板上钉钉&a…

(ubuntu)安装nginx

文章目录 前言回顾Linux命令在线安装&#xff1a;相关命令&#xff1a;相关路径常用配置&#xff1a; 卸载nginxbug相关: 前言 提示&#xff1a;别再问我的规划是什么了&#xff1a;呼吸&#xff0c;难道不算一个吗&#xff1f; --E.M齐奥朗 回顾Linux命令 # 查看当前进程的所…

pdf误删恢复如何恢复?分享4种恢复方法!

如何将pdf误删恢复&#xff1f;使用电脑的时候&#xff0c;经常会需要使用到pdf文件&#xff0c;但是有时候&#xff0c;因为一些操作上的失误&#xff0c;我们会丢失一些重要的文件。如果你不小心将pdf误删了&#xff0c;该如何进行恢复呢&#xff1f; PDF文件丢失的原因可以…

Jenkins部署失败:JDK ‘jdk1.8.0_381‘ not supported to run Maven projects

Jenkins部署报错&#xff1a;JDK ‘jdk1.8.0_381’ not supported to run Maven projects提示使用的jdk有问题&#xff0c;启动的jdk版本不能满足项目启动。 登录Jenkins管理页面&#xff0c;系统管理——全局工具配置——JDK安装配置满足条件的JDK版本&#xff0c;保存配置&…

YOLOv7改进:新颖的上下文解耦头TSCODE,即插即用,各个数据集下实现暴力涨点

💡💡💡本文属于原创独家改进:上下文解耦头TSCODE,进行深、浅层的特征融合,最后再分别输入到头部进行相应的解码输出,实现暴力暴力涨点 上下文解耦头TSCODE| 亲测在多个数据集实现暴力涨点,对遮挡场景、小目标场景提升也明显; 收录: YOLOv7高阶自研专栏介绍: …

零售数据分析模板分享(通用型)

零售数据来源多&#xff0c;数据量大&#xff0c;导致数据的清洗整理工作量大&#xff0c;由于零售的特殊性&#xff0c;其指标计算组合更是多变&#xff0c;进一步导致了零售数据分析工作量激增&#xff0c;往往很难及时分析数据&#xff0c;发现问题。那怎么办&#xff1f;可…

模仿企业微信界面

备注&#xff1a;未实现相关功能&#xff0c;仅模仿界面&#xff0c;不能作为商业用途&#xff0c;若有侵权&#xff0c;请联系删除。 <Window x:Class"模仿企业微信界面.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"…

CVE-2023-46227 Apache inlong JDBC URL反序列化漏洞

项目介绍 Apache InLong&#xff08;应龙&#xff09;是一站式、全场景的海量数据集成框架&#xff0c;同时支持数据接入、数据同步和数据订阅&#xff0c;提供自动、安全、可靠和高性能的数据传输能力&#xff0c;方便业务构建基于流式的数据分析、建模和应用。 项目地址 h…

Linux系统安装redis并配置为服务

一、Linux环境 1、下载 官网提供的源码下载地址&#xff1a; https://github.com/redis/redis/archive/7.0.5.tar.gz 2、将源码上传至服务器 3、解压缩 # 将解压缩后的文件放置在同目录的source文件夹下 tar -zxvf redis-7.0.5.tar.gz -C ./source4、编译安装 对源码进行编…

FFmpeg 解析Glide 缓存下的图片文件报错(Impossible to open xxx)

简单介绍下背景 我们业务有个功能把图片放到一个文件中&#xff0c;统一进行播放 &#xff0c;但是遇到一个棘手问题&#xff0c;某一个情况下 的图片 就是打不开 就是报错。以为是编译参数 。哪些格式没有加上。但经过测试 该加的都加了。 所以 不是编译参数的问题。 Impossi…

ElasticSearch:实现高效数据搜索与分析的利器!项目中如何应用落地,让我带你实操指南。

1.难点解答 收集到几个问题&#xff1a; elasticsearch是单独建一个项目&#xff0c;作为全文搜索使用&#xff0c;还是直接在项目中直接用&#xff1f; ES 服务器是要单独部署的&#xff0c;你可以把 ES 理解为 Redis。 新增数据时&#xff0c;插入到mysql中&#xff0c;需不…

Webpack 基础以及常用插件使用方法

目录 一、前言二、修改打包入/出口配置步骤 三、常用插件使用html-webpack-plugin打包 CSS 代码提取 CSS 代码优化压缩过程打包 less 代码打包图片文件 一、前言 本质上&#xff0c;Webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时…

多级缓存入门

文章目录 什么是多级缓存JVM进程缓存环境准备安装MySQL导入Demo工程导入商品查询页面 初识Caffeine Lua语法初识Lua第一个lua程序变量和循环Lua的数据类型声明变量循环 条件控制、函数函数条件控制 多级缓存安装OpenRestyOpenResty快速入门反向代理流程OpenResty监听请求编写it…

【数据结构】堆的详解

文章目录 堆的简介堆的实现堆的插入数据堆的删除数据 堆排序向上调整和向下调整的时间复杂度的分析 大量数据的topk问题 堆的简介 今天要写的数据结构是堆&#xff0c;什么是堆呢&#xff1f;堆其实是一种完全二叉树&#xff0c;只不过它是有条件的。 堆分为两种&#xff0c;一…