Sentinel上下文创建及执行
Sentinel上下文创建及执行,入口示例代码:
public static void fun() {
    Entry entry = null;
    try {
        entry = SphU.entry(SOURCE_KEY);
    } catch (BlockException e1) {
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
}
执行entry
在执行SphU.entry时获取Entry,Entry代表当前调用的入口,用来保存当前调用信息。
进入到SphU.entry方法可以发现,Entry的获取使用的是Sph的默认实现CtSph。Sph是资源统计和规则检查的接口定义。
public class Env {
    public static final Sph sph = new CtSph();
    static {
        // If init fails, the process will exit.
        InitExecutor.doInit();
    }
}
进到CtSph.entry方法:
@Override
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entry(resource, count, args);
}
可以看出第一步是创建一个当前资源的包装类,然后将标识当前请求资源的包装类传进entry方法获取Entry。值得一提的是StringResourceWrapper继承自ResourceWrapper。而ResourceWrapper重新了hashCode和equals方法,如下:
@Override
public int hashCode() {
    return getName().hashCode();
}
@Override
public boolean equals(Object obj) {
    if (obj instanceof ResourceWrapper) {
        ResourceWrapper rw = (ResourceWrapper)obj;
        return rw.getName().equals(getName());
    }
    return false;
}
可以看出比较两个Warpper是否指向同一个资源,主要是比较的name,只要获取的资源名相同那么就是要求获取同一个资源,这一点在后面有用。
然后回到CtSph.entry方法,最终进入到了CtSph.entryWithPriority方法,代码如下:
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    // 1.从当前线程获取context
    Context context = ContextUtil.getContext();
    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);
    }
    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);
    }
    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;
}
该方法做了如下几件事:
- 首先尝试从当前线程获取context,可以看ContextUtil.getContext方法: - public static Context getContext() {
 return contextHolder.get();
 }
 - 查看contextHolder属性是一个ThreadLocal: - /**
 * Store the context in ThreadLocal for easy access.
 */
 private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
 
- 判断当前线程上下文是否超出了阈值,也就是下面语句: - if (context instanceof NullContext)
 - 我们可以看看NullContext的定义: - /**
 * If total {@link Context} exceed {@link Constants#MAX_CONTEXT_NAME_SIZE}, a
 * {@link NullContext} will get when invoke {@link ContextUtil}.enter(), means
 * no rules checking will do.
 *
 * @author qinan.qn
 */
 public class NullContext extends Context { public NullContext() {
 super(null, "null_context_internal");
 }
 }
 - 当上面判断为真时,那么就不再进行规则检查。 
- 当从当前线程获取的Context为空时,创建新的Context。**(这里在下面再详细解读。) ** 
- 获取当前资源对应的Slot执行链: - ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
 - 在获取执行链的方法:CtSph.lookProcessChain: - ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
 ProcessorSlotChain chain = chainMap.get(resourceWrapper);
 if (chain == null) {
 synchronized (LOCK) {
 chain = chainMap.get(resourceWrapper);
 if (chain == null) {
 // Entry size limit.
 if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
 return null;
 } chain = SlotChainProvider.newSlotChain();
 Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
 chainMap.size() + 1);
 newMap.putAll(chainMap);
 newMap.put(resourceWrapper, chain);
 chainMap = newMap;
 }
 }
 }
 return chain;
 }
 - 其中chainMap定义: - private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
 = new HashMap<ResourceWrapper, ProcessorSlotChain>();
 - 可以看出每个资源都是对应的一个执行链,在chainMap中就是用ResourceWrapper做为键类型,而我们上面已经看到了ResourceWrapper重写了hashCode和equals方法,所以唯一确定一个资源的就是资源名。 
- 执行Slot链,如果规则检查未通过那么抛出BlockException异常,否则代表符合规则进入成功。 
然后接下来看一下Context的创建,也就是下面这段代码:
if (context == null) {
    // Using default context.
    context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
跟踪代码,最终进入到了ContextUtil.trueEnter方法。在阅读ContextUtil.trueEnte方法时,有必要先看一张图,来理清一下线程thread和Context、Context和Node之间的关系:
图片来源(该文章可以一看):https://www.jianshu.com/p/e39ac47cd893

前面代表的是3个线程,可以看成他们都是获取helloWorld资源,可以看出每一个线程在执行的时候都是独立的创建了一个Context,每一个线程里面的Context都是对应到了一个点EntranceNode上,而该EntranceNode则是用于存储一个资源的信息。梳理了这三者之间的关系,那么接下来看ContextUtil.trueEnt方法,代码如下:
protected static Context trueEnter(String name, String origin) {
    Context context = contextHolder.get();
    if (context == null) {
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        DefaultNode 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 {
                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                            // Add entrance node.
                            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 = new Context(node, name);
        context.setOrigin(origin);
        contextHolder.set(context);
    }
    return context;
}
这个方法做的事情如下:
- 从contextHolder中获取Context,如果有了那么就直接返回。 
- 没有Context信息,那么准备开始创建该上下文信息,准备工作:从contextNameNodeMap中获取对应节点,contextNameNodeMap定义如下: - private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
 - 这个map中是上下文名和节点的对应关系,而上下文名即是资源名。 
- 如果获取到了这个节点,那么直接创建一个Context并设置到contextHolder中,然后直接返回。 
- 当上面节点不存在,那么先创建该节点,逻辑如下: - 先检查当前上下文数是否超过指定阈值,如果超过了那么返回NullContext,本次请求不做规则检查。 
- 没有超过指定阈值,那么加锁,进行双重检查。 
- 使用当前资源创建节点,将创建的节点关联到根节点下,然后存入contextNameNodeMap中。然后创建Context并返回。可以看看在新增节点的时候,它的做法是在原map的基础上新建一个size+1的新map,然后将原map的所有节点信息加入新map中,同时保存新节点信息: - Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
 newMap.putAll(contextNameNodeMap);
 newMap.put(name, node);
 contextNameNodeMap = newMap;
 - 而我们的contextNameNodeMap属性是用volatile进行修饰的,当contextNameNodeMap引用的值发生变更时,能够立即对其它线程可见。 - 那么为什么不在原来的contextNameNodeMap中直接加入新节点,而要新建map然后进行一次复制呢? 
 
做完上面这些事情后,我们就得到了需要的Context。
执行exit
不管是上面示例代码中的finally里面的entry.exit()调用,还是CtSph.entryWithPriority方法中调用的e.exit(count, args)方法,最终都是在CtEntry.exitForContext方法中执行,代码如下:
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
    if (context != null) {
        // Null context should exit without clean-up.
        if (context instanceof NullContext) {
            return;
        }
        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 {
            // Go through the onExit hook of all slots.
            if (chain != null) {
                chain.exit(context, resourceWrapper, count, args);
            }
            // Go through the existing terminate handlers (associated to this invocation).
            callExitHandlersAndCleanUp(context);
            // Restore the call stack.
            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();
                }
            }
            // Clean the reference of context in current entry to avoid duplicate exit.
            clearEntryContext();
        }
    }
}
逻辑比较简单,执行chain.exit,清空context等。
Sentinel上下文创建及执行的更多相关文章
- ASP.NET Web API 过滤器创建、执行过程(二)
		ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ... 
- ASP.NET Web API 过滤器创建、执行过程(一)
		ASP.NET Web API 过滤器创建.执行过程(一) 前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就 ... 
- 1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This
		参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p ... 
- linux进程编程:子进程创建及执行函数简介
		linux进程编程:子进程创建及执行函数简介 子进程创建及执行函数有三个: (1)fork();(2)exec();(3)system(); 下面分别做详细介绍.(1)fork() 函数定 ... 
- (转)ASP.NET Mvc 2.0 - 1. Areas的创建与执行
		转自:http://www.cnblogs.com/terrysun/archive/2010/04/13/1711218.html ASP.NET Mvc 2.0 - 1. Areas的创建与执行 ... 
- JBPM4入门——6.流程实例的创建和执行
		本博文只是简要对JBPM4进行介绍,如需更详细内容请自行google 链接: JBPM入门系列文章: JBPM4入门——1.jbpm简要介绍 JBPM4入门——2.在eclipse中安装绘制jbpm流 ... 
- 创建可执行的JAR包
		创建可执行的JAR文件包,需要使用带cvfm参数的jar命令,命令如下:JAR cvfm test.jar manifest.mf testtest.jar和manifest.mf为两个文件,分别对应 ... 
- SpringBoot 创建可执行Jar
		创建可执行JAR 我们也可以通过插件创建一个在生产环境中运行的可执行jar文件来完成我们的示例. 首先引入依赖: <build> <plugins> <plugin> ... 
- 使用jar命令打jar/war包、创建可执行jar包、运行jar包、及批处理脚本编写
		jar 命令 jar 是一个jar.exe可执行命令,即可以生成jar文件,也可以生成war文件 使用示例:jar -cvf ../xxx.jar * -c create,创建新的归档文档 ... 
随机推荐
- Spring @Scheduled Annotation
			1.Overview 这里我们将会学习Spring @Scheduled 标签,了解它是如何配置,如何设置定时任务. 关于它的使用,有两点简单的规则需要记住: ※它的方法应该是一个void返回值类型 ... 
- selenium  IDE使用-1
			selenium 硒 Mercury汞,外国人喜欢取这化学的名字 一.selenium概述 1.selenium是开源免费的,针对web应用程序功能自动化测试的工作. 2.做功能自动化的原因:回归测试 ... 
- [水题日常]UVA1625 	Color Length
			来整理一下思路- 一句话题意:给两个大写字母的序列,每次取出其中一个数列的第一个元素放到新序列里面,对每个字母\(c\)记它的跨度\(L(c)\)为这个字母最后出现的位置-第一次出现的位置,求新序列所 ... 
- YZMCMS V5.3后台 SSRF
			当改变命运的时刻降临,犹豫就会败北. 前言 此前在测试过程中遇到过此CMS,久攻不下,于是便尝试代码审计,不得不说这套CMS还是挺安全的,读起来也简单,也适合初学代码审计的同学去阅读,不过漏洞真的不多 ... 
- 【原创】Linux PCI驱动框架分析(一)
			背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ... 
- B. Navigation System【CF 1320】
			传送门 题目:简单理解就是,我们需要开车从s点到t点.车上有一个导航,如果当前点为x,则导航会自动为你提供一条从x到t的最短的路线(如果有多条,则随机选一条),每走到下一个点则会实时更新最短路线,当然 ... 
- 开发你的第一个NCS(Zephyr)应用程序
			Nordic有2套并存的SDK:老的nRF5 SDK和新的NCS SDK,两套SDK相互独立,大家选择其中一套进行开发即可.一般而言,如果你选择的芯片是nRF51或者nRF52系列,那么推荐使用nRF ... 
- .netcore3.1——应用AutoMapper
			多层架构中存在多种模型,如视图模型ViewModel,数据传输对你DTO,ORM对象等,这些数据在层与层之间进行传输必须涉及类型之间的转换. AutoMapper是一个对象-对象映射器,作用是通过设置 ... 
- Git常用命令大全,迅速提升你的Git水平
			原博文 https://mp.weixin.qq.com/s/hYjGyIdLK3UCEVF0lRYRCg 示例 初始化本地git仓库(创建新仓库) git init ... 
- PIX
			[开启]后,如图: [新建]:如图中设定: Program: 你要准备监测的应用程序路径 [点击]:Start Experiment 如图,会出现一个新窗口(你运行的应用程序窗口) [点击F12](确 ... 
