当前位置:首页 >> 硬件技术 >> 【Sentinel】核心API-Entry与Context,华为c8813

【Sentinel】核心API-Entry与Context,华为c8813

cpugpu芯片开发光刻机 硬件技术 1
文件名:【Sentinel】核心API-Entry与Context,华为c8813 【Sentinel】核心API-Entry与Context

文章目录 一、Entry1、Entry的声明2、使用API自定义资源3、基于@SentinelResource注解标记资源 二、Context1、Context介绍2、Context的初始化3、AbstractSentinelInterceptor4、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服务中,将OrderService的queryOrderById()方法标记为一个资源: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*/@Aspectpublic 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`实现:

@Overridepublic 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的执行,见下篇。

协助本站SEO优化一下,谢谢!
关键词不能为空
同类推荐
«    2025年12月    »
1234567
891011121314
15161718192021
22232425262728
293031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接