sentinel调用上下文(Context)数据结构分析

sentinel是一个轻量级高可用流量控制组件,使用sentinel来进行流量控制的简单demo如下:

Entry entry = null;

try {
    entry = SphU.entry("abc");
    entry = SphU.entry("def");
} catch (BlockException e1) {

} finally {
    if (entry != null) {
        entry.exit();
    }
}

将字符串“abc”抽象成一个资源,然后调用接口的时候进行限制。进入SphU.entry分析一下。

public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
        return Env.sph.entry(name, type, count, args);
    }


public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
        return entryWithPriority(resourceWrapper, count, false, args);
    }


private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        //获取上下文对象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);
        }
        // 获取该资源对应的SlotChain

        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 {
            // 执行Slot的entry方法
            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;
    }

在函数 ContextUtil.getContext()中获取上下文对象context

1)首先在ThreadLocal获取,获取不到就创建,不然就返回

2)然后再Map中根据ContextName找一个Node

3)没有找到Node就加锁的方式,创建一个EntranceNode,然后放入Map中

4)创建Context,设置node,name,origin,再放入ThreadLocal中

Context的结构如下

在函数entryWithPriority中,获取Context后就创建调用链slot chain。

ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

然后创建CtEntry

Entry e = new CtEntry(resourceWrapper, chain, context);

Entry e = new CtEntry(resourceWrapper, chain, context);

class CtEntry extends Entry {
    protected Entry parent = null;
    protected Entry child = null;

    protected ProcessorSlot<Object> chain;
    protected Context context;
    protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;

    CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
        super(resourceWrapper);
        this.chain = chain;
        this.context = context;

        setUpEntryFor(context);
    }

    private void setUpEntryFor(Context context) {
        // The entry should not be associated to NullContext.
        if (context instanceof NullContext) {
            return;
        }
        // 首次返回为null
        this.parent = context.getCurEntry();
        if (parent != null) {
            ((CtEntry) parent).child = this;
        }
        // 设置Context的curEntry为当前的CtEntry
        context.setCurEntry(this);
    }
}

在CtEntry中设置context的curEntry,Context的结构就变成下面这样

调用函数chain.entry,第一次执行NodeSelectorSlot方法

@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {

    private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {

        DefaultNode node = map.get(context.getName());
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                if (node == null) {
                    node = new DefaultNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                    cacheMap.putAll(map);
                    cacheMap.put(context.getName(), node);
                    map = cacheMap;
                    // 构建调用树,添加entranceNode的子节点
                    ((DefaultNode) context.getLastNode()).addChild(node);
                }

            }
        }
        // 设置Context的当前节点
        context.setCurNode(node);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
}

public class Context {
    private final String name;
    private DefaultNode entranceNode;
    private Entry curEntry;
    private String origin = "";
    private final boolean async;

    public Node getLastNode() {
        if (curEntry != null && curEntry.getLastNode() != null) {
            return curEntry.getLastNode();
        } else {
            return entranceNode;
        }
    }

    public Context setCurNode(Node node) {
        this.curEntry.setCurNode(node);
        return this;
    }
}

 当第二次调用entry = SphU.entry("abc");第二次执行NodeSelectorSlot时

因为资源名不同所以对应的职责链条,因此NodeSelectorSlot会重新创建DefaultNode。

通过上面的代码分析,可以看出context的数据结构的演变如下:

1、创建context

刚开始初始化的时候,context中的curEntry属性是没有值的,如下图所示:

2、然后创建Entry

每创建一个新的Entry对象时,都会重新设置context的curEntry,并将context原来的curEntry设置为该新Entry对象的父节点,如下图所示:

3、退出Entry

某个Entry退出时,将会重新设置context的curEntry,当该Entry是最顶层的一个入口时,将会把ThreadLocal中保存的context也清除掉,如下图所示:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值