暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Sentinel的秘书Context上下文

安全链 2021-09-05
1053
一、Context定义

Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点entranceNode、本次调用链路的 curNode、调用来源origin等信息。Context 名称即为调用链路入口名称。Sentinel将上下文放在ThreadLocal中存储。在源码中经常看到
Context context = contextHolder.get();,该代码即为在线程中获取上下文Context。

com.alibaba.csp.sentinel.context.ContextUtil
图1  ContextUtil

二、创建Context

Context创建只发生在两个地方,第一个是
ContextUtil初始化时,第二个是调用ContextUtil.enter(name, origin)

ContextUtil初始化

package com.alibaba.csp.sentinel.context;
/**
 * Utility class to get or create {@link Context} in current thread.
 * Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}.
 * If we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used.
 */

public class ContextUtil {
              /*省略部分代码。。。。。。*/
    static {
        // Cache the entrance node for default context.
        initDefaultContext();
    }

    private static void initDefaultContext() {
        String defaultContextName = Constants.CONTEXT_DEFAULT_NAME;
       /*初始化一个sentinel_default_context,type为in的队形*/
        EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null);
       /*Constants.ROOT会初始化一个name是machine-root,type=IN的对象*/
        Constants.ROOT.addChild(node);
       /*map添加key=CONTEXT_DEFAULT_NAME的对象*/
        contextNameNodeMap.put(defaultContextName, node);
    }
}
             /*省略部分代码。。。。。。*/

ContextUtil初始化时会执行static代码
块,initDefaultContext方法会创建一个名为
sentinel_default_context的EntranceNode做为节点接入ROOT节点。并把该节点放入contextNameNodeMap中,该map添加了volatile关键字,轻量级的线程之间可以互相看见的共享操作。

trueEnter调用


ContextUtil.enter(name, origin)本质是调用了trueEnter,trueEnter先从ThreadLocal中获取Context,若不存在则创建一个以name为名的node,作为Context的EntranceNode,也就是上下文的入口。最终完成创建Context。

com.alibaba.csp.sentinel.context.ContextUtil
protected static Context trueEnter(String name, String origin) {
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            /*在contextNameNodeMap中查找是否有已存在的node*/
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();
                    return NULL_CONTEXT;
                } else {
                    try {
                        LOCK.lock();
                 /*类似initDefaultContext方法的内容,但不一样*/
                        node = contextNameNodeMap.get(name);
                        if (node == null) {
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();
                                return NULL_CONTEXT;
                            } else {
                 /*此处创建的node名字为ContextUtil.enter中的name*/
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                /*以下代码提升迭代稳定性,防止脏读*/
                                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;
    }

三、总结

最后还是要强调一下整个Context过程中,对于资源的识别主要依靠contextNameNodeMap这个map,不同线程中对于资源的统计数据都与Context有关,如果被线程隔离,那么这个统计数据也就没什么意义了,正是因为volatile加持着contextNameNodeMap,不同线程才可以共享这个map,成全了对于资源统计的有效性。





Sentinel源码(流量入口)

史上最易懂Sentinel入门

文章转载自安全链,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论