Sentinel之Slots插槽源碼分析(一)

一轨帜、概述

前面介紹過(guò)Sentinel核心框架就是通過(guò)插槽鏈一層層的調(diào)用瘩将,每個(gè)插槽的功能如下:

  • NodeSelectorSlot 負(fù)責(zé)收集資源的路徑凯傲,并將這些資源的調(diào)用路徑锐涯,以樹(shù)狀結(jié)-構(gòu)存儲(chǔ)起來(lái)磕诊,用于根據(jù)調(diào)用路徑來(lái)限流降級(jí)。
  • ClusterBuilderSlot 則用于存儲(chǔ)資源的統(tǒng)計(jì)信息以及調(diào)用者信息,例如該資源的 RT, QPS, thread count 等等秀仲,這些信息將用作為多維度限流融痛,降級(jí)的依據(jù)。
  • StatisticSlot 則用于記錄神僵、統(tǒng)計(jì)不同緯度的 runtime 指標(biāo)監(jiān)控信息。
  • LogSlot 則用于記錄blockException信息的日志信息覆劈,會(huì)寫(xiě)入的日志文件中保礼。
  • ParamFlowSlot 則用于根據(jù)熱點(diǎn)參數(shù)進(jìn)行限流控制的。
  • SystemSlot 則通過(guò)系統(tǒng)的狀態(tài)责语,例如 load1 等炮障,來(lái)控制總的入口流量。
  • AuthoritySlot 則根據(jù)配置的黑白名單和調(diào)用來(lái)源信息坤候,來(lái)做黑白名單控制胁赢。
  • FlowSlot 則用于根據(jù)預(yù)設(shè)的限流規(guī)則以及前面 slot 統(tǒng)計(jì)的狀態(tài),來(lái)進(jìn)行流量控制白筹。
  • DegradeSlot 則通過(guò)統(tǒng)計(jì)信息以及預(yù)設(shè)的規(guī)則智末,來(lái)做熔斷降級(jí)。

這邊文章會(huì)先介紹NodeSelectorSlot和ClusterBuilderSlot功能

二徒河、節(jié)點(diǎn)Node

我們知道在進(jìn)行資源的限流降級(jí)中系馆,需要拿到Entry,Entry就像是一個(gè)憑證顽照,拿到這個(gè)憑證由蘑,代碼才能繼續(xù)走下去。Entry又被封裝到上下文對(duì)象Context中代兵。在Context中還有一個(gè)entranceNode節(jié)點(diǎn)尼酿,代表這個(gè)資源調(diào)用樹(shù)一個(gè)入口。Entry中又有curNode植影、originNode這兩個(gè)屬性裳擎。curNode保存了這一次調(diào)用統(tǒng)計(jì)信息,riginNode保存了在這個(gè)上下文中何乎,所有的調(diào)用的統(tǒng)計(jì)信息和句惯。

有這么多Node,那他們之間的關(guān)系是怎樣的支救,如圖:

Node
  • Node是一個(gè)接口抢野,有很多各種指標(biāo)的方法,Sentinel就用通過(guò)這些指標(biāo)進(jìn)行限流降級(jí)的各墨。
  • StatisticNode:統(tǒng)計(jì)實(shí)時(shí)統(tǒng)計(jì)指標(biāo)的Node指孤。有兩個(gè)子類(lèi)DefaultNode和ClusterNode。
  • EntranceNode:EntranceNode是每個(gè)上下文的入口,該節(jié)點(diǎn)是掛在root下的恃轩,是全局唯一的结洼,每一個(gè)context都會(huì)對(duì)應(yīng)一個(gè)entranceNode。
  • DefaultNode:DefaultNode是記錄當(dāng)前調(diào)用實(shí)時(shí)數(shù)據(jù)的叉跛,在同一個(gè)上下文中不同資源關(guān)聯(lián)著不同的DefaultNode松忍。在同一個(gè)上下文中,對(duì)不同的資源調(diào)用筷厘,DefaultNode會(huì)有子childNode生成鸣峭。
  • ClusterNode:全局的統(tǒng)計(jì)數(shù)據(jù),包括 rt, thread count, qps等酥艳,相同的資源關(guān)DefaultNode聯(lián)著統(tǒng)一個(gè)ClusterNode摊溶,無(wú)論它在哪個(gè)上下文中。

三充石、NodeSelectorSlot

Sentinel之Entry構(gòu)建源碼解析 這篇文章介紹過(guò)莫换,Entry可以理解Context這棵樹(shù)的樹(shù)干,那么NodeSelectorSlot這個(gè)插槽可以理解為正式構(gòu)建entry葉子而形成的骤铃,下面具體分析拉岁。

    //注意這里的可以使context的name
    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 = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                    cacheMap.putAll(map);
                    cacheMap.put(context.getName(), node);
                    map = cacheMap;
                }
                // Build invocation tree
                ((DefaultNode)context.getLastNode()).addChild(node);
            }
        }

        context.setCurNode(node);
  }

分析

  • 1、從map中獲取改context下的defaultNode劲厌,如果defaultNode不存在膛薛,到2,反之直接到4补鼻。
  • 2哄啄、雙重鎖校驗(yàn)defaultNode存不存,若不存在风范,則創(chuàng)建一個(gè)defaultNode咨跌,并重新構(gòu)造map,并添加到map中硼婿,然后到3锌半。
  • 3、獲取context的當(dāng)前調(diào)用Node的LastNode寇漫,并把該node添加到子Node中刊殉。
  • 4、重新設(shè)置Context的curEntry的curNode州胳。

看圖分析
在一個(gè)Context中第一次進(jìn)入時(shí)记焊,在第三步,調(diào)用Context的getLastNode方法栓撞。

//在Context中
public Node getLastNode() {
      //curEntry.getLastNode() 調(diào)用CtEntry的方法getLastNode
        if (curEntry != null && curEntry.getLastNode() != null) {
            return curEntry.getLastNode();
        } else {
            return entranceNode;
        }
}

在CtEntry中

 @Override
    public Node getLastNode() {
        return parent == null ? null : parent.getCurNode();
    }

上篇文章分析過(guò)Entry的構(gòu)建過(guò)程遍膜,第一次調(diào)用時(shí):

Entry

可以發(fā)現(xiàn)curEntry != null條件滿(mǎn)足碗硬,但是parent是null。所以瓢颅,lastNode第一次的值就是context的entranceNode恩尾,然后將node添加到entranceNode中,并設(shè)置curEntry的curNode節(jié)點(diǎn)挽懦。然后Context內(nèi)存結(jié)構(gòu)變成如下翰意。

defaultNode

接著又一個(gè)請(qǐng)求進(jìn)入,若資源不同信柿,在生成一個(gè)新的Entry后(上一篇分析過(guò))猎物。
這個(gè)時(shí)候再次調(diào)用context.getLastNode()將會(huì)返回parent.getCurNode(),把這個(gè)節(jié)點(diǎn)放入到lastNode的子節(jié)點(diǎn)中角塑,再把node設(shè)置給context的curEntry。最后Context內(nèi)存結(jié)構(gòu)變成如下淘讥。

defaultNode

這里假設(shè)的是資源名不同的情況圃伶,若是資源相同的話(huà),則context.getLastNode()).addChild(node);邏輯就不會(huì)執(zhí)行蒲列,只會(huì)把當(dāng)前node設(shè)置為context的curEntry中窒朋。這時(shí)的內(nèi)存結(jié)構(gòu)如圖。

defaultNode

上面分析了在NodeSelectorSlot中CurEntry葉子構(gòu)建的過(guò)程蝗岖,在一次構(gòu)建中會(huì)設(shè)置entranceNode的childNode侥猩。

資源路徑收集

經(jīng)過(guò)上述分析可以發(fā)現(xiàn),NodeSelectorSlot主要負(fù)責(zé)收集資源的路徑抵赢,并將這些資源的調(diào)用路徑欺劳,以樹(shù)狀結(jié)構(gòu)存儲(chǔ)起來(lái),用于根據(jù)調(diào)用路徑來(lái)限流降級(jí)铅鲤。

 ContextUtil.enter("entrance1", "appA");
 Entry nodeA = SphU.entry("nodeA");
 if (nodeA != null) {
    nodeA.exit();
 }
 ContextUtil.exit();

上述代碼通過(guò) ContextUtil.enter() 創(chuàng)建了一個(gè)名為 entrance1 的上下文划提,同時(shí)指定調(diào)用發(fā)起者為 appA;接著通過(guò) SphU.entry()請(qǐng)求一個(gè) token邢享,如果該方法順利執(zhí)行沒(méi)有拋 BlockException鹏往,表明 token 請(qǐng)求成功。

以上代碼將在內(nèi)存中生成以下結(jié)構(gòu):

              machine-root
                 /     
                /
         EntranceNode1
              /
             /   
      DefaultNode(nodeA

注意:每個(gè) DefaultNode 由資源 ID 和輸入名稱(chēng)來(lái)標(biāo)識(shí)骇塘。換句話(huà)說(shuō)伊履,一個(gè)資源 ID 可以有多個(gè)不同入口的 DefaultNode。

 ContextUtil.enter("entrance1", "appA");
  Entry nodeA = SphU.entry("nodeA");
  if (nodeA != null) {
    nodeA.exit();
  }
  ContextUtil.exit();

  ContextUtil.enter("entrance2", "appA");
  nodeA = SphU.entry("nodeA");
  if (nodeA != null) {
    nodeA.exit();
  }
  ContextUtil.exit();

以上代碼將在內(nèi)存中生成以下結(jié)構(gòu):

                   machine-root
                    /       \
                   /         \
             Entrance1     Entrance2
                /             \
               /               \
      DefaultNode(nodeA)   DefaultNode(nodeA)
               |                    |
      +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);

可以發(fā)現(xiàn)同一個(gè)nodeA資源共用一個(gè)ClusterNode款违,而不管它在哪一個(gè)上線(xiàn)文中唐瀑。接下面講解ClusterBuilderSlot,來(lái)看ClusterNode構(gòu)造奠货。

四介褥、ClusterBuilderSlot

ClusterBuilderSlot插槽用于構(gòu)建資源的 ClusterNode 以及調(diào)用來(lái)源節(jié)點(diǎn)。ClusterNode 保持資源運(yùn)行統(tǒng)計(jì)信息(響應(yīng)時(shí)間、QPS柔滔、block 數(shù)目溢陪、線(xiàn)程數(shù)、異常數(shù)等)以及原始調(diào)用者統(tǒng)計(jì)信息列表睛廊。來(lái)源調(diào)用者的名字由 Context.enter(contextName形真,origin) 中的 origin 標(biāo)記。

看源碼:

public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap
        = new HashMap<ResourceWrapper, ClusterNode>();

    private static final Object lock = new Object();

    private ClusterNode clusterNode = null;

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args)
        throws Throwable {
        if (clusterNode == null) {
            synchronized (lock) {
                if (clusterNode == null) {
                    // Create the cluster node.
                    clusterNode = Env.nodeBuilder.buildClusterNode();
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<ResourceWrapper, ClusterNode>(16);
                    newMap.putAll(clusterNodeMap);
                    newMap.put(node.getId(), clusterNode);

                    clusterNodeMap = newMap;
                }
            }
        }
        node.setClusterNode(clusterNode);

        /*
         * if context origin is set, we should get or create a new {@link Node} of
         * the specific origin.
         */
        if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

//以下代碼省略
}

細(xì)心地人可以發(fā)現(xiàn)ClusterBuilderSlot集成的AbstractLinkedProcessorSlot的泛型對(duì)象由Object變?yōu)镈efaultNode了超全,這是因?yàn)镈efaultNode已經(jīng)在NodeSelectorSlot插槽中構(gòu)建好了咆霜。

來(lái)看ClusterBuilderSlot插槽的entry方法做了哪些事。

  • 1嘶朱、判斷clusterNode是否為空蛾坯,若為空,到2疏遏,反之到3脉课。
  • 2、創(chuàng)建一個(gè)clusterNode節(jié)點(diǎn)财异,并添加到clusterNodeMap中倘零,注意這里的map的可以使ResourceWrapper,用以表示不區(qū)分在哪個(gè)上下文中戳寸,到3呈驶。
  • 3、設(shè)置clusterNode到defaultNode中疫鹊,到4袖瞻。
  • 4、如果context的origin不為空订晌,則把originNode設(shè)置到Context當(dāng)前cutEntry的originNode中虏辫。這個(gè)originNode用于后續(xù)根據(jù)調(diào)用源進(jìn)行限流或資源保護(hù)。

五锈拨、我的小結(jié)

1砌庄、本文是插槽分析的第一篇,介紹的NodeSelectorSlot和ClusterBuilderSlot的作用奕枢。
2娄昆、NodeSelectorSlot用來(lái)構(gòu)建Context的curEntry的葉子節(jié)點(diǎn),不同的資源id在不同的上下文中有不同的入口缝彬,并且對(duì)應(yīng)不同的defaultNode萌焰,但同一個(gè)資源對(duì)應(yīng)同一個(gè)clusterNode。
3谷浅、ClusterBuilderSlot設(shè)置了defaultNode的clusterNode扒俯,并設(shè)置了Entry的originNode奶卓。clusterNode保存clusterNodeMap中,可以發(fā)現(xiàn)系統(tǒng)運(yùn)行的時(shí)間越長(zhǎng)撼玄,這個(gè)map就越穩(wěn)定夺姑。
4、NodeSelectorSlot和ClusterBuilderSlot是整個(gè)插槽鏈中的前兩個(gè)插槽掌猛,這個(gè)兩個(gè)插槽完成Context數(shù)據(jù)的封裝和對(duì)資源調(diào)用鏈Node包裝盏浙,以便對(duì)后續(xù)數(shù)據(jù)的收集和資源的保護(hù)限流。


以上內(nèi)容荔茬,若有不當(dāng)住處废膘,請(qǐng)指正

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慕蔚,隨后出現(xiàn)的幾起案子丐黄,更是在濱河造成了極大的恐慌,老刑警劉巖孔飒,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孵稽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡十偶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)园细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惦积,“玉大人,你說(shuō)我怎么就攤上這事猛频∈ū溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鹿寻,是天一觀的道長(zhǎng)睦柴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)毡熏,這世上最難降的妖魔是什么坦敌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮痢法,結(jié)果婚禮上狱窘,老公的妹妹穿的比我還像新娘。我一直安慰自己财搁,他們只是感情好蘸炸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著尖奔,像睡著了一般搭儒。 火紅的嫁衣襯著肌膚如雪穷当。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天淹禾,我揣著相機(jī)與錄音馁菜,去河邊找鬼。 笑死稀拐,一個(gè)胖子當(dāng)著我的面吹牛火邓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播德撬,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铲咨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蜓洪?” 一聲冷哼從身側(cè)響起纤勒,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隆檀,沒(méi)想到半個(gè)月后摇天,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恐仑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年泉坐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裳仆。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腕让,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歧斟,到底是詐尸還是另有隱情纯丸,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布静袖,位于F島的核電站觉鼻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏队橙。R本人自食惡果不足惜坠陈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捐康。 院中可真熱鬧畅姊,春花似錦、人聲如沸吹由。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)倾鲫。三九已至粗合,卻和暖如春萍嬉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隙疚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工壤追, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人供屉。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓行冰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親伶丐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悼做,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容