文档章节

Alibaba Sentinel 源码阅读(Part 1 执行流程)

OrangeJoke
 OrangeJoke
发布于 2018/10/20 22:45
字数 1654
阅读 369
收藏 0

准备

  1. 已经看过wiki 很多遍。
  2. 跑过测试用例和example,已经理解Sentinel的作用和大致的工作原理。
  3. 此博客基于版本 0.2.0,请自行准备源码。

目标

带着疑惑和问题阅读源码,理解未知的问题,和设计的优缺点。 这个项目的源码分析,直接从 SphU.entry(resource, EntryType); 入口方法往下走就可以了,比较直接。

工作原理

因为这张图很好的解释了工作原理,所以引用到这里,具体文档看wiki工作原理

重要概念

Resource : 需要保护的资源
Node : 存储资源的访问信息,资源的qps等信息
Slot : 独立功能的可插拔模块,处理具体的业务,比如限流处理等...。

Q1. Sentinel 怎么存储资源的访问信息?

Node的创建

先看看信息统计的栗子:

curl http://localhost:18990/tree?type=root

EntranceNode: machine-root(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: sentinel_default_context(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: book(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
--book(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
---/book/coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
--coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
---/coke/exception(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
---/coke/coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)

每个资源都有一个统计信息,对应存储到Node上,但是上一级的信息会是下一级信息的一个统计。 先看看Node的结构:

再来看看不同的node 功能和创建时机。


// Entrance node create in {@link ContextUtil} (对应上面的EntranceNode: book)
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<String, DefaultNode>();
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
// Default node create in {@link NodeSelectorSlot} (对应上面的 --book, --/book/coke),同时会把这些资源的调用路径,以树状结构存储起来。
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
// Build invocation tree
((DefaultNode)context.getLastNode()).addChild(node);

// Cluster node build in {@link ClusterBuilderSlot}(Cluter node的功能会单独讲)
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap
        = new HashMap<ResourceWrapper, ClusterNode>();
clusterNode = Env.nodeBuilder.buildClusterNode();
node.setClusterNode(clusterNode);

Node的信息收集

到上面为止,我们的node就创建好了,之后StatisticSlot 就会进行计数和统计了

// 各个规则的校验
fireEntry(context, resourceWrapper, node, count, args);
// 如果通过就统计相应的指标
 node.increaseThreadNum();
 node.addPassRequest();
 
if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest();
            }
 if (resourceWrapper.getType() == EntryType.IN) {
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest();
            }

for (ProcessorSlotEntryCallback<DefaultNode> handler :StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }

Node中统计信息的使用

我们以FlowControl 为栗子, 先为 /book/coke 这个资源创建FlowControl(流量控制)的规则。

[{"controlBehavior":0,"count":1.0,"grade":1,"limitApp":"default","maxQueueingTimeMs":500,"resource":"/book/coke","strategy":0,"warmUpPeriodSec":10}]

规则校验时获取的信息. StatisticSlot

  // 获取qps信息
    @Override
    public long passQps() {
        return rollingCounterInSecond.pass() / IntervalProperty.INTERVAL;
    }

我们可以看到 统计信息都是从 StatisticSlot 中获取,而最终的数据都是存在 rollingCounterInMinuterollingCounterInMinute 中的,这也核心的数据结构和算法所在,我们再下一篇具体再看

private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT,
        IntervalProperty.INTERVAL);

    /**
     * Holds statistics of the recent 120 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,
     * meaning each bucket per second, in this way we can get accurate statistics of each second.
     */
    private transient Metric rollingCounterInMinute = new ArrayMetric(1000, 2 * 60);

Q2. Slot Chain 是怎么工作的?

通过上面Node 创建过程,可以看到Node 的创建也是由各个Slot完成,每个Slot完成不同的功能然后向后传递。

Slot Chain创建过程

构造chain 在类CtSph 中创建

// 创建slot chain
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

 /**
     * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
     * be created if the resource doesn't relate one.
     *
     * <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
     * {@link ProcessorSlotChain} globally, no matter in witch {@link Context}.<p/>
     *
     * <p>
     * Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
     * otherwise null will return.
     * </p>
     *
     * @param resourceWrapper target resource
     * @return {@link ProcessorSlotChain} of the resource
     */
    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;
                    }
					// 创建SlotChain的方法
                    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;
    }

来看看default的实现

/**
 * Helper class to create {@link ProcessorSlotChain}.
 *
 * @author qinan.qn
 * @author leyou
 */
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        chain.addLast(new NodeSelectorSlot());
        chain.addLast(new ClusterBuilderSlot());
        chain.addLast(new LogSlot());
        chain.addLast(new StatisticSlot());
        chain.addLast(new SystemSlot());
        chain.addLast(new AuthoritySlot());
        chain.addLast(new FlowSlot());
        chain.addLast(new DegradeSlot());

        return chain;
    }

}

同时提供了自定义 builder的SPI 功能,SPI机制单独一个章节我们再看

 private static void resolveSlotChainBuilder() {
        List<SlotChainBuilder> list = new ArrayList<SlotChainBuilder>();
        boolean hasOther = false;
		// LOADER 就是对应的SPI
        for (SlotChainBuilder builder : LOADER) {
            if (builder.getClass() != DefaultSlotChainBuilder.class) {
                hasOther = true;
                list.add(builder);
            }
        }
        if (hasOther) {
            builder = list.get(0);
        } else {
            // No custom builder, using default.
            builder = new DefaultSlotChainBuilder();
        }

        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
            + builder.getClass().getCanonicalName());
    }

Slot Chain 的调用过程

Slot Chain 创建好了之后,那就从 firt开始 一个个往下执行就好了,这个流程,有接口直接定义好了,接口描述其实非常清晰,也就不再赘述。

public interface ProcessorSlot<T> {

    /**
     * Entrance of this slot.
     *
     * @param context         current {@link Context}
     * @param resourceWrapper current resource
     * @param param           Generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node}
     * @param count           tokens needed
     * @param args            parameters of the original call
     * @throws Throwable blocked exception or unexpected error
     */
    void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args)
        throws Throwable;

    /**
     * Means finish of {@link #entry(Context, ResourceWrapper, Object, int, Object...)}.
     *
     * @param context         current {@link Context}
     * @param resourceWrapper current resource
     * @param obj             relevant object (e.g. Node)
     * @param count           tokens needed
     * @param args            parameters of the original call
     * @throws Throwable blocked exception or unexpected error
     */
    void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args)
        throws Throwable;

    /**
     * Exit of this slot.
     *
     * @param context         current {@link Context}
     * @param resourceWrapper current resource
     * @param count           tokens needed
     * @param args            parameters of the original call
     */
    void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);

    /**
     * Means finish of {@link #exit(Context, ResourceWrapper, int, Object...)}.
     *
     * @param context         current {@link Context}
     * @param resourceWrapper current resource
     * @param count           tokens needed
     * @param args            parameters of the original call
     */
    void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);

总结

根据工作原理,顺着entry 方法把Sentinel的大致流程过了一遍,可能是因为设计比较合理,所以源码阅读起来比较轻松。整体过程就是构建一个个slot,每个slot处理对应的职责,并将其连起来。资源以树状的形式组织,并将统计信息存储在对应的节点上。下一篇我们将看下具体信息的存储部分,也是重要的数据结构部分。

© 著作权归作者所有

OrangeJoke
粉丝 40
博文 57
码字总数 39185
作品 0
江北
高级程序员
私信 提问
Spring Cloud 微服务架构实战(三)——使用alibaba-sentinel流量控制、熔断降级

什么是Sentinel 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel 具有以下特...

xiaomin0322
06/05
133
0
公益:开放一台Nacos服务端给各位Spring Cloud爱好者

之前开放过一台公益Eureka Server给大家,以方便大家在阅读我博客中教程时候做实验。由于目前在连载Spring Cloud Alibaba,所以对应的也部署了一台Nacos,并且也开放出来,给大家学习测试之用...

程序猿DD
05/14
127
0
sentinel-apollo拉模式

代码地址:https://github.com/zhaoyunxing92/spring-boot-learn-box/tree/master/spring-boot-sentinel/sentinel-apollo 使用apollo为sentinel做数据持久化,sentinel的使用可以看sentinel使用......

zhaoyunxing
05/15
0
0
Sentinel 1.6.3 正式发布,引入网关流控控制台的支持

Sentinel 1.6.3 正式发布,引入网关流控控制台的支持,同时带来一些 bug 修复和功能改进,欢迎使用! Release Notes: https://github.com/alibaba/Sentinel/releases/tag/1.6.3 新版本特性介...

阿里巴巴中间件
07/30
2.7K
2
聊聊sentinel的SentinelWebAutoConfiguration

序 本文主要研究一下sentinel的SentinelWebAutoConfiguration SentinelWebAutoConfiguration spring-cloud-alibaba-sentinel-autoconfigure-0.2.0.BUILD-SNAPSHOT-sources.jar!/org/springf......

go4it
2018/08/15
85
0

没有更多内容

加载失败,请刷新页面

加载更多

Shell学习记录(持续更新)

一、shell定时备份数据库任务通用脚本 目标:根据定时任务启动脚本,执行数据库备份任务,按照日期进行每日备份,如已经备份则脚本停止,备份任务完成后将结果发送邮件提醒 1.执行数据库备份...

网络小虾米
今天
3
0
PHP计算两个经纬度地点之间的距离

/** * 求两个已知经纬度之间的距离,单位为米 * * @param lng1 $ ,lng2 经度 * @param lat1 $ ,lat2 纬度 * @return float 距离,单位米 * @author www.Alixixi.com */function get...

子枫Eric
今天
14
0
Linux—day 4

ch2 需要掌握的命令 (1)cat -n 1.txt (2)more 1.txt (3)head -n 15 initial-setup-ks.cfg (4)tail -n 17 initial-setup-ks.cfg;tail -f initial-setup-ks.cfg (5)cat -n anaconda-ks.c......

呵呵暖茶
今天
31
0
【Kubernetes社区之路】我的PR被抢了

2019年11月的某天,我无意间发现一个PR作者在自己的PR中抱怨自己的PR没被合入,而另一个比自己提交晚且内容几乎一样的PR则被合入了。 字里行间透露些许伤感外加无奈,原文如下: 作为一名开源...

恋恋美食
今天
40
0
阻塞队列

对于许多线程问题, 可以通过使用一个或多个队列以优雅且安全的方式将其形式化。生产者线程向队列插人元素, 消费者线程则取出它们。 使用队列, 可以安全地从一个线程向另 一个线程传递数据...

ytuan996
今天
48
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部