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;
}

该方法做了如下几件事:

  1. 首先尝试从当前线程获取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<>();
  2. 判断当前线程上下文是否超出了阈值,也就是下面语句:

    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");
    }
    }

    当上面判断为真时,那么就不再进行规则检查。

  3. 当从当前线程获取的Context为空时,创建新的Context。**(这里在下面再详细解读。) **

  4. 获取当前资源对应的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方法,所以唯一确定一个资源的就是资源名。

  5. 执行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;
}

这个方法做的事情如下:

  1. 从contextHolder中获取Context,如果有了那么就直接返回。

  2. 没有Context信息,那么准备开始创建该上下文信息,准备工作:从contextNameNodeMap中获取对应节点,contextNameNodeMap定义如下:

    private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();

    这个map中是上下文名和节点的对应关系,而上下文名即是资源名。

  3. 如果获取到了这个节点,那么直接创建一个Context并设置到contextHolder中,然后直接返回。

  4. 当上面节点不存在,那么先创建该节点,逻辑如下:

    1. 先检查当前上下文数是否超过指定阈值,如果超过了那么返回NullContext,本次请求不做规则检查。

    2. 没有超过指定阈值,那么加锁,进行双重检查。

    3. 使用当前资源创建节点,将创建的节点关联到根节点下,然后存入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上下文创建及执行的更多相关文章

  1. ASP.NET Web API 过滤器创建、执行过程(二)

    ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ...

  2. ASP.NET Web API 过滤器创建、执行过程(一)

    ASP.NET Web API 过滤器创建.执行过程(一) 前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就 ...

  3. 1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

    参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p ...

  4. linux进程编程:子进程创建及执行函数简介

    linux进程编程:子进程创建及执行函数简介 子进程创建及执行函数有三个: (1)fork();(2)exec();(3)system();    下面分别做详细介绍.(1)fork()    函数定 ...

  5. (转)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的创建与执行 ...

  6. JBPM4入门——6.流程实例的创建和执行

    本博文只是简要对JBPM4进行介绍,如需更详细内容请自行google 链接: JBPM入门系列文章: JBPM4入门——1.jbpm简要介绍 JBPM4入门——2.在eclipse中安装绘制jbpm流 ...

  7. 创建可执行的JAR包

    创建可执行的JAR文件包,需要使用带cvfm参数的jar命令,命令如下:JAR cvfm test.jar manifest.mf testtest.jar和manifest.mf为两个文件,分别对应 ...

  8. SpringBoot 创建可执行Jar

    创建可执行JAR 我们也可以通过插件创建一个在生产环境中运行的可执行jar文件来完成我们的示例. 首先引入依赖: <build> <plugins> <plugin> ...

  9. 使用jar命令打jar/war包、创建可执行jar包、运行jar包、及批处理脚本编写

    jar 命令 jar 是一个jar.exe可执行命令,即可以生成jar文件,也可以生成war文件   使用示例:jar -cvf ../xxx.jar *  -c   create,创建新的归档文档 ...

随机推荐

  1. ARM开发工具下载地址汇总

    一,下载地址 1,ARM DS5官方下载地址https://developer.arm.com/tools-and-software/embedded/legacy-tools/ds-5-develo ...

  2. 浏览器开发者工具network详解

    General概诉 请求链接 Request URL: 请求方式 Request Method: 代码状态 Status Code: 远程地址 Remote Address: 引用协议 用于过滤 Re ...

  3. [水题日常]Luogu1113 杂务

    这几天又在颓. 我的blog难度目前还比较低,主要面向像我这样子的新手(当然内容也会尽量讲得具体清楚一些)x 如果有错误还请指出~ 写完随笔之后才去翻了一下这题题解之后才注意到这题是有序的 QAQ完全 ...

  4. Java CPU占用过高之诊断

    Redhat, java 先用top 命令(默认为cpu占用排序), 确认java占用CPU kill -3 <java pid> ps Hh -eo pid,tid,pcpu | sor ...

  5. Ubuntu不能直接从windows复制粘贴文件或文字

    终端输入: apt-get autoremove open-vm-tools sudo apt-get install open-vm-tools-desktop 然后重启电脑就可以了. 参考链接:h ...

  6. Flowable—多实例任务:会签

    多实例任务 会签 什么是会签? 举个例子:比如我们有一个任务 可能需要多人审批,审批通过的条件可能比较多样,比如五个人审批.3个人审批过了就算过了,再或者有一个人权限比较高,拥有一票否决权. 即是其余 ...

  7. git 只提交部分文件 拉取pull远程仓库

    正常的团队开发中,在提交代码push之前要先pull拉取远程仓库的代码.而拉取pull操作需要先commit修改的文件才能成功. 那么问题来了,当我只想push提交部分代码的时候,同样需要pull拉取 ...

  8. 微信小程序-页面下拉

    微信小程序当滑动到最顶部和最底部时,继续下拉,会将整个页面拉下去或者拉上去,本来以为是客户端自有的特性,就没去管他,直到我的禅道出现了这个记录... 其实这个问题是可以解决的,只需要在你不想出现在此情 ...

  9. Map集合,Map常用子类

    Map 集合 1,Collection中的集合,元素是孤立的,向季和忠储存的元素采用一个元素方式储存 2,Map中的集合,元素是成对存在的,每个元素中的集合称为双列集合 3,Collection中的集 ...

  10. JAVA Executor(线程池)框架

    一.Executor概述 为更好控制线程,jdk提供一套线程管理框架Executor,帮助开发人员有效地进行线程控制.它们都位于java.util.concurrent包中,是jdk并发包的核心.其中 ...