【Sentinel】核心API-Entry与Context

文章目录

  • 一、Entry
    • 1、Entry的声明
    • 2、使用API自定义资源
    • 3、基于@SentinelResource注解标记资源
  • 二、Context
    • 1、Context介绍
    • 2、Context的初始化
    • 3、AbstractSentinelInterceptor
    • 4、ContextUtil

一、Entry

1、Entry的声明

默认情况下,Sentinel会将controller中的方法作为被保护资源,那么问题来了,我们该如何将自己的一段代码标记为一个Sentinel的资源呢?

Sentinel中的资源用Entry来表示。

声明Entry的API示例:(try后面直接加括号写上对应的资源,就不用自己再加finally语句去关闭了,即try-with-resource

// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {// 被保护的业务逻辑// 你想做的操作,比如异常次数+1、调用次数+1// do something here...
} catch (BlockException ex) {// 业务代码抛的异常+限流的异常// 资源访问阻止,被限流或被降级// 在此处进行相应的处理操作
}

try-with-resource的写法需要注意:

特别地,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。

不用try-with-resource,手动exit的写法:

Entry entry = null;
// 务必保证 finally 会被执行
try {// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效entry = SphU.entry("自定义资源名");// 被保护的业务逻辑// do something...
} catch (BlockException ex) {// 资源访问阻止,被限流或被降级// 进行相应的处理操作
} catch (Exception ex) {// 若需要配置降级规则,需要通过这种方式记录业务异常Tracer.traceEntry(ex, entry);
} finally {// 务必保证 exit,务必保证每个 entry 与 exit 配对if (entry != null) {entry.exit();}
}

关于SphU.entry()方法的参数:

在这里插入图片描述
注意:SphU.entry(xxx) 需要与 entry.exit() 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。常见的错误:

1、自定义埋点只调用 SphU.entry(),没有调用 entry.exit()
2、顺序错误,比如:entry1 -> entry2 -> exit1 -> exit2,应该为 entry1 -> entry2 -> exit2 -> exit1

2、使用API自定义资源

在demo工程的order-service服务中,将OrderServicequeryOrderById()方法标记为一个资源:queryOrderById()方法未修改前:

在这里插入图片描述

  • 首先处理下依赖与配置
<!--sentinel-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:cloud:sentinel:transport:dashboard: localhost:8090 # 这里我的sentinel用了8089的端口
  • 修改OrderService类的queryOrderById方法,创建Entry资源,将要保护的一段代码放入try语句来做为一个资源:
public Order queryOrderById(Long orderId) {// 创建Entry,标记资源,资源名为resource1try (Entry entry = SphU.entry("resource1")) {// 1.查询订单Order order = orderMapper.findById(orderId);// 2.查询用户,基于Feign的远程调用User user = userClient.findById(order.getUserId());// 3.设置order.setUser(user);// 4.返回return order;}catch (BlockException e){log.error("被限流或降级", e);return null;}
}

重启后可以看到自定义资源成功:

在这里插入图片描述
很明显,上面的逻辑在自定义资源时是重复的动作,即创建Entry,再拿个try-catch把要定义的代码包起来。 ⇒ AOP环绕实现,在前面之前try,在之后catch,AOP再兑换成一个注解,这个注解官方已实现,就是@SentinelResource

3、基于@SentinelResource注解标记资源

同样标记Service里的queryOrderById方法:

在这里插入图片描述
从自动装配开始找到源码看下实现:

在这里插入图片描述

匹配@SentinelResource注解做AOP环绕增强的源码:


/*** Aspect for methods with {@link SentinelResource} annotation.** @author Eric Zhao*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {// 切点是添加了 @SentinelResource注解的类@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")public void sentinelResourceAnnotationPointcut() {}// 环绕增强@Around("sentinelResourceAnnotationPointcut()")public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {// 获取受保护的方法Method originMethod = resolveMethod(pjp);// 获取 @SentinelResource注解SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);if (annotation == null) {// Should not go through here.throw new IllegalStateException("Wrong state for SentinelResource annotation");}// 获取注解上的资源名称String resourceName = getResourceName(annotation.value(), originMethod);EntryType entryType = annotation.entryType();int resourceType = annotation.resourceType();Entry entry = null;try {// 创建资源 Entryentry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());// 执行受保护的方法Object result = pjp.proceed();return result;} catch (BlockException ex) {return handleBlockException(pjp, annotation, ex);} catch (Throwable ex) {Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();// The ignore list will be checked first.if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {throw ex;}if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {traceException(ex);return handleFallback(pjp, annotation, ex);}// No fallback function can handle the exception, so throw it out.throw ex;} finally {if (entry != null) {entry.exit(1, pjp.getArgs());}}}
}

简单来说,@SentinelResource注解就是一个标记,而Sentinel基于AOP思想,对被标记的方法做环绕增强,完成资源(Entry)的创建。

二、Context

在这里插入图片描述
如图,除了簇点链路中的controller方法,以及我自定义的资源,还有一个默认的入口节点:sentinel_spring_web_context,它是一个EntranceNode类型的节点,这个节点是在初始化Context的时候由Sentinel帮我们创建的。

1、Context介绍

  • Context 代表调用链路上下文,贯穿一次调用链路中的所有资源( Entry),基于ThreadLocal。
  • Context 维护着入口节点(entranceNode)、本次调用链路的 curNode(当前资源节点)、调用来源(origin)等信息。
  • 后续的Slot都可以通过Context拿到DefaultNode或者ClusterNode,从而获取统计数据,完成规则判断
  • Context初始化的过程中,会创建EntranceNode,contextName就是EntranceNode的名称

创建Context的API为:

// 创建context,包含两个参数:context名称、 来源名称
ContextUtil.enter("contextName", "originName");

2、Context的初始化

查看Sentinel依赖包下的spring.factories:
在这里插入图片描述

spring.factories声明需要就是自动装配的配置类:

在这里插入图片描述
先看SentinelWebAutoConfiguration这个类:

在这里插入图片描述

这个类实现了WebMvcConfigurer,我们知道这个是SpringMVC自定义配置用到的类,可以配置HandlerInterceptor:

在这里插入图片描述
可以看到这里泛型中配置了一个SentinelWebInterceptor的拦截器。SentinelWebInterceptor的声明如下:

在这里插入图片描述
发现它继承了AbstractSentinelInterceptor这个类,而AbstractSentinelInterceptor最终实现了HandlerInterceptor

在这里插入图片描述
HandlerInterceptor拦截器会拦截一切进入controller的方法,执行preHandle前置拦截方法,而Context的初始化就是在这里完成的。

3、AbstractSentinelInterceptor

HandlerInterceptor拦截器会拦截一切进入controller的方法,执行preHandle前置拦截方法,而Context的初始化就是在这里完成的。来看看这个类的preHandle`实现:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {// 获取资源名称,一般是controller方法的@RequestMapping路径,例如/order/{orderId}String resourceName = getResourceName(request);if (StringUtil.isEmpty(resourceName)) {return true;}// 从request中获取请求来源,将来做 授权规则 判断时会用String origin = parseOrigin(request);// 获取 contextName,默认是sentinel_spring_web_contextString contextName = getContextName(request);// 创建 ContextContextUtil.enter(contextName, origin);// 创建资源,名称就是当前请求的controller方法的映射路径Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);return true;} catch (BlockException e) {try {handleBlockException(request, response, e);} finally {ContextUtil.exit();}return false;}
}

4、ContextUtil

创建Context的方法就是 ContextUtil.enter(contextName, origin); 我们进入entry方法:

public static Context enter(String name, String origin) {if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {throw new ContextNameDefineException("The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");}return trueEnter(name, origin);
}

进入trueEnter方法:

protected static Context trueEnter(String name, String origin) {// 尝试获取contextContext context = contextHolder.get();// 判空if (context == null) {// 如果为空,开始初始化Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;// 尝试获取入口节点DefaultNode node = localCacheNameMap.get(name);if (node == null) {LOCK.lock();try {node = contextNameNodeMap.get(name);if (node == null) {// 入口节点为空,初始化入口节点 EntranceNodenode = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);// 添加入口节点到 ROOTConstants.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,参数为:入口节点 和 contextNamecontext = new Context(node, name);// 设置请求来源 origincontext.setOrigin(origin);// 放入ThreadLocalcontextHolder.set(context);}// 返回return context;
}

画图表示Entry和Context两个核心API完成资源的创建,不管是controller中的资源还是自定义的资源,接下来就是执行ProcessorSlotChain插槽链,关于ProcessorSlotChain的执行,见下篇。

在这里插入图片描述

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

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

相关文章

Flink实时计算中台Kubernates功能改造点

背景 平台为数据开发人员提供基本的实时作业的管理功能,其中包括jar、sql等作业的在线开发;因此中台需要提供一个统一的SDK支持平台能够实现flink jar作业的发布;绝大多数情况下企业可能会考虑Flink On Yarn的这个发布模式,但是伴随云原生的呼声越来越大,一些企业不希望部…

Java之文件操作与IO

目录 一.认识文件 1.1文件是什么&#xff1f; 1.2文件的组织 1.3文件路径 1.4文件的分类 二.文件操作 2.1File概述 三.文件内容操作--IO 3.1JavaIO的认识 3.2Reader和Writer ⭐Reader类 ⭐Writer类 3.2FileInputStream和FileOutputStream ⭐FileInputStream类 …

Pycharm中出现ImportError:DLL load failed:找不到指定模块的解决方法

不论搭建什么工程&#xff0c;运行什么文件&#xff0c;只要在Pycharm中出现ImportError: DLL load failed: 找不到指定的模块这样的问题&#xff0c;以下方法都适用&#xff01;&#xff01;&#xff01; 一、问题描述 我在使用pycharm连接webots&#xff0c;用python控制机…

设计模式-装饰模式

文章目录 一、简介二、基本概念三、装饰模式的结构和实现类图解析&#xff1a;装饰器的实现方式继承实现&#xff1a;组合实现&#xff1a;继承和组合对比 四、装饰模式的应用场景五、与其他模式的关系六、总结 一、简介 装饰模式是一种结构型设计模式&#xff0c;它允许动态地…

【Unity-Cinemachine相机】相机跟随之Transposer属性

相机跟随和瞄准行为 Transposer&#xff1a;虚拟相机将在某个固定的偏移或距离上跟随目标移动 上面的偏移量就是Follow Offset Binding Mode决定Follow Offset是目标本地坐标系下的身后十米还是世界坐标系下的身后十米 Lock To Target On Assign&#xff1a;锁定自己和目标本地…

【Git-Exception】Git报错:fatal: unable to auto-detect email address

报错信息&#xff1a; *** Please tell me who you are. Run git config --global user.email “youexample.com” git config –global user.name “Your Name” to set your account’s default identity. Omit --global to set the identity only in this repository. fatal…

Unity汉化一个插件 制作插件汉化工具

我是编程一个菜鸟&#xff0c;英语又不好&#xff0c;有的插件非常牛&#xff01;我想学一学&#xff0c;页面全是英文&#xff0c;完全不知所措&#xff0c;我该怎么办啊...尝试在Unity中汉化一个插件 效果&#xff1a; 思路&#xff1a; 如何在Unity中把一个自己喜欢的插件…

nmp ERR! code ERR SOCKET TIMEOUT nmp ERR!network npmSocket timeout(已解决)

当安装vue-cli时&#xff0c;出现超时错误 npm ERR! code ECONNRESET npm ERR! network This is a problem related to network connectivity npm ERR! code ECONNRESET npm ERR! network aborted npm ERR! network This is a problem related to network connectivity. npm E…

如何判断自己的qt版本呢?

如何判断自己的qt版本呢? 前情提要很简单,按照如下图所示,即可查看当前打开的qtCreator的版本如何打开5.15.2版本的qtCreator呢?安装教程 前情提要 我的电脑已经安装了qt5.14.1,然后我又安装了qt5.15.2,我想尝试一下同一台电脑能否适应两个版本的qt? 当我安装完成qt5.15.2后…

Mac Homebrew中常用的 Brew 命令

Mac 中常用的 Brew 命令集 Brew&#xff08;Homebrew&#xff09;是一个强大的包管理器&#xff0c;用于在 macOS 上安装、更新和管理各种软件包。它使得在 Mac 上安装开发工具、应用程序和库变得轻松和便捷。本博客将介绍一些在 Mac 中常用的 Brew 命令&#xff0c;以帮助您更…

Astro建站教程:安装nodejs,npm下载Astro,安装扩展

下载Nodejs LTS版&#xff1a;https://nodejs.org/en 安装步骤全默认即可&#xff0c;安装路径可以根据自己的爱好更改在桌面右键打开cmd或powershell&#xff0c;输入node -v和npm -v测试是否安装成功 浏览器打开https://docs.astro.build/en/install/auto/ 复制里面的npm cre…

MyBatisPlus入门篇2 - 条件查询、查询投影、查询条件、id生成策略、多记录操作、逻辑删除

目录 1.条件查询、多条件查询 MyBatisPlus将书写复杂的SQL查询条件进行了封装&#xff0c;使用编程的形式完成查询条件的组合。 Test void testGetByCondition() {// 方式一&#xff1a;按条件查询QueryWrapper<User> qw new QueryWrapper<User>();qw.lt("…