ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Sentinel源码之源码入口和构建Context

2022-05-21 10:34:48  阅读:170  来源: 互联网

标签:return Context 源码 context Sentinel null annotation name


1.分析源码入口

在微服务使用Sentinel实际工作场景中,我们只需要引入对应依赖:spring-cloud-starter-alibaba-sentinel就会进行自动装配,所以我们直接看META-INF/spring.factories,然后这里从SentinelAutoConfiguration开始看起

image

Sentinel是通过AOP的方式进行切入的,从这里我们看到了Aspect关键字,所以我们就从这里跟进去

@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
 return new SentinelResourceAspect();
}

跟进去以后我们就会发现这里就是再利用AOP通过@SentinelResource为注解来作为切入点,进行切入

@Aspect //切面
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

 //指定切入点为SentinelResource注解
 @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
 public void sentinelResourceAnnotationPointcut() {
 }
...
}

再往下跟我们会发现,还有@Around注解进行环绕通知,其实这就是是利用@SentinelResource注解作为切点,然后在通过AOP环绕通知,来进行增强,在执行原方法前,来执行对应操作,当然这里我们可以看出,一旦出现了限流或者限流就会走BlockException。

// 环绕通知
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
 Method originMethod = resolveMethod(pjp);

 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 {
     // 创建资源操作对象
     entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
     // 调用原方法
     return pjp.proceed();
 } 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());
     }
 }
}

在以上的这段代码中还有一个问题,我们在之前就说过在创建资源操作对象的时候我们需要先创建Context,但是明显这里没有显示创建,但是实际上我们如果看Context概念的话,就会知道,如果程序中未指定Context,会创建name为"sentinel_default_context"的默认Context,然后我们继续往下跟踪。

public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args) throws BlockException {
 //限流方法
 return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
}

进入到entry方法中,这里的entryWithType方法就是我们要看的真正的限流的方法,具体的实现方法在com.alibaba.csp.sentinel.CtSph.entryWithType

@Override
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args) 
throws BlockException {
 return entryWithType(name, resourceType, entryType, count, false, args);
}

@Override
public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
                     Object[] args) throws BlockException {
 // 这里将资源的名称和信息封装称为资源对象
 StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
 // 返回一个Entry资源操作对象
 // prioritized属性表示优先级,默认值为false,表示当前请求不按照优先级执行,直接执行
 return entryWithPriority(resource, count, prioritized, args);
}

我们接下来来具体分析核心方法entryWithPriority

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
 // 从当前线程中获取Context
 // 一个请求会占用一个线程,并且绑定一个Context
 Context context = ContextUtil.getContext();
 // 一个请求对应一个Context
 // 如果当前类型为NullContext,表示此时请求已经超出了阈值,无需检测规则
 if (context instanceof NullContext) {
     // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
     // so here init the entry only. No rule checking will be done.
     return new CtEntry(resourceWrapper, null, context);
 }

	 // 此时如果获取Context为空,就创建默认的sentinel_default_context,并且会放入到当前线程中
 if (context == null) {
     // Using default context.
     context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
 }

// 判断全局开关,如果是关闭状态,直接返回无需检测规则
 // Global switch is close, no rule checking will do.
 if (!Constants.ON) {
     return new CtEntry(resourceWrapper, null, context);
 }
 /**
        * 这里是整个架构的核心所在,这里是在构建一个处理链,这个处理链是一个单向链表结构,类似于Filter一样,构建这个链条的
     * 原因是对业务进行解耦,像限流资源保护有很多,比如限流、降级、热点参数、系统降级等等,如果都写在一起就耦合很严重,我们知道oop的
     * 思想就是让每个类确定各自的职责,不要让他做不相干的事情,所以这里将业务进行全面解耦,然后在解耦的同时又通过链式编程将它们串起来
     */
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    /*
         * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
         * so no rule checking will be done.
         */
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        // 针对资源操作
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // This should not happen, unless there are errors existing in Sentinel internal.
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}

2.构建Context

分析这个类型中的InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);

/**
* This class is used for skip context name checking.
此类型是用于跳过Context名称的检测
*/
private final static class InternalContextUtil extends ContextUtil {
    static Context internalEnter(String name) {
        // 从这里继续跟踪
        return trueEnter(name, "");
    }

    static Context internalEnter(String name, String origin) {
        return trueEnter(name, origin);
    }
}

首先这里要明确一下,一个Context的组成实际上需要name(名称)和origin(来源),所以方法上传入这两个参数

protected static Context trueEnter(String name, String origin) {
    // 从当前线程中获取当前context名称
    Context context = contextHolder.get();
    // 如果当前context为空
    if (context == null) {
        // 从缓存中获取,当前缓存中key值为:Context名称,value值为:EntranceNode
        // (因为后续创建的是EntranceNode),需要它的原因是因为构建Context需要EntranceNode
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        // 在缓存中获取EntranceNode
        DefaultNode node = localCacheNameMap.get(name);
        // 如果node为空
        if (node == null) {
            // 当前缓存的size>Context的最大数量,返回NULL_Context类型
            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赋值为EntranceNode
                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                            // Add entrance node.
                            // 将新建的EntranceNode添加到ROOT中
                            Constants.ROOT.addChild(node);
                            // 将新建的EntranceNode添加到缓存中
                            Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                            newMap.putAll(contextNameNodeMap);
                            newMap.put(name, node);
                            contextNameNodeMap = newMap;
                        }
                    }
                } finally {
                    LOCK.unlock();
                }
            }
        }
        // 将name和node封装成Context
        context = new Context(node, name);
        // 设定来源
        context.setOrigin(origin);
        // 将context写入到当前线程中
        contextHolder.set(context);
    }
	// 返回Context
    return context;
}

标签:return,Context,源码,context,Sentinel,null,annotation,name
来源: https://www.cnblogs.com/ZT-666/p/16294543.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有