Sentinel Core流程分析

 上次介紹了Sentinel的基本概念,並在文章的最後介紹了基本的用法。此次將對用法中的主要流程和實現作說明,該部分主要涉及到源碼中的sentinel-core模塊。node

1.token獲取

file

  如上爲token獲取的主流程,首先會先獲取線程的上下文對象Context,而後根據ResourceName查找對應的處理槽鏈,得到SlotChain後,生成該次調用動做的Entry對象,該對象會關聯對應SlotChain。內部會調用SlotChain的entry方法,讓entry動做進入每一個槽,後續須要調用Entry的exit方法,讓exit動做進入SlotChain的每一個槽。windows

  其中第三步生成的Entry對象爲CtEntry對象,其模型上是一個鏈表,會將每次entry動做生成的Entry對象串聯起來緩存

file

  如上圖,每new一個CtEntry,都會傳入context對象。因爲每次操做會將當前Entry賦值context的curEntry,每次new一次時,會檢查該屬性,若是爲空,則是第一個節點,直接複製給curEntry;若是非空,則該值爲上一個節點,將該值複製給當前值的parent,並將該值的child指向當前節點。作完這些動做後將context的curEntry指向當前節點。具體過程如上圖示。數據結構

  執行entry.exit,內部會判斷context.curEntry是不是執行時的entry,此舉是爲了控制exit順序保持後進先出。若是判斷不經過,說明不是按照後進先出的順序執行exit,會從執行的entry開始,到根節點逐個進行exit,並拋出異常。若是判斷經過,則調用對應的SlotChain執行exit,並更改context.curEntry,將其指向當前節點的父節點,但不解除Entry鏈的關係。併發

file

2.查找處理槽鏈

  執行時會先從本地的緩存中查找是否已經有該資源對應的處理槽鏈,若是沒有,則從新新生成一個。新加載時,使用SPI,查找系統提供的SlotChainBuilder實現,如有除默認的DefaultSlotChainBuilder以外的實如今,則使用第一個,不然使用默認的Builder。默認的Builder提供的處理槽鏈以下函數

file

3.執行處理槽鏈

  槽的處理過程以下:高併發

file

  ProcessorSlotChain爲一個鏈表,執行slot的entry方法會進入到Slot的內部,在內部能夠經過fireEntry執行鏈表中下一個slot的entry方法(若是存在)。如上,在fireEntry以前和以後能夠有每一個slot本身的處理邏輯,從而造成了相似過濾器鏈的結構。同理,exit過程也相似性能

3.1. NodeSelectorSlot

  負責收集資源的路徑,並將這些資源的調用路徑,以樹狀結構存儲起來,用於根據調用路徑來限流降級;該動做發生在fireEntry動做前。ui

  以下代碼將構建出相對應的調用路徑:線程

file

file

  執行SphU.entry時會先獲取線程上下文對應的Context,若是沒有則新增一個。對於node1C,直接調用SphU.entry,會自動生成一個默認的Context,內部會調用ContextUtil.enter,並設置EntranceNode(sentinel_default_context),而後將該EntranceNode接入到虛擬EntranceNode(machine-root)的子節點列表中。對於node2A和node3A,因爲調用了ContextUtil.enter,至關於顯示指定了Context,並設置了EntraceNode(entrance1)和EntraceNode(entrance2)。SphU.entry在沒有指定EntryType時,將設置EntryType爲OUT。 實現代碼以下:

file

  實現上,因爲同一個資源共享同一個ProcessorSlotChain對象,於是不一樣Context調用同一個資源時會使用到同一個NodeSelectorSlot對象。代碼中直接使用ContextName至關因而使用了ResourceName-ContextName進行判斷。爲了在對應的Context下構建調用鏈,內部維護了一個Map<String,DefaultNode>,其中key爲對應線程上下文的ContextName,value爲該上下文調用鏈中的各個Node。對於第一次訪問的資源,會在對應的Context鏈下新增一個Node,並將該節點作爲子節點連接到鏈上最近訪問的那個節點上,從而完成調用鏈的構建。對於重複出現的資源,只會使用第一次出現的順序。在該slot得到的Node節點將傳入後續各個槽進行處理。

3.2.ClusterBuilderSlot

  用於存儲資源的統計信息以及調用者信息,例如該資源的 RT, QPS, thread count 等等,這些信息將用做爲多維度限流,降級的依據;該動做發生在fireEntry動做前。

  上面的例子通過該slot後將新增以下ClusterNode節點

file

  上面說過,因爲同一個資源共享同一個ProcessorSlotChain對象,於是不一樣Context調用同一個資源時會使用到同一個NodeSelectorSlot對象,爲了統計該種資源的Cluster信息,直接使用一個ClusterNode節點表示便可。 實現上,ClusterBuilderSlot還持有一個靜態的ClusterNodeMap,用於緩存全部資源的ClusterNode信息。當通過該slot時,會判斷Map中是否有該資源的節點信息,沒有則新建一個。

file

  上面還有一段內容是設置節點的Origin信息節點的內容。以下圖,ClusterNode統計了同一種資源的統計信息,而不區分不一樣的Context來源,內部使用originCountMap區分不一樣的來源的統計狀況。 對於默認的sentinel_default_context,其orgin設置爲空(""),於是Cluster沒有該Context的Origin信息

file

3.3.LogSlot

  用於打印日誌,在發生限流或者降級時輸出特定日誌;該動做發生在fireEntry動做後。

3.4.StatisticSlot

  用於記錄、統計不一樣緯度的 runtime 指標監控信息;該動做發生在fireEntry動做後。

  該Slot的動做發生在fireEntry後,根據上面SlotChain執行圖,該動做會在後續Slot檢查執行後再執行。後續檢查包括了權限檢查,系統指標,用戶自定義的限流和降級規則。

  以下圖,該Slot的動做以下:

file

  若成功通過後續各個Slot的檢查,至關於得到了token,則會更新統計信息,包括增長線程數(ThreadNum),增長經過請求數(PassRequest),涉及的節點包括:

  1. 當前節點;當前節點的Origin節點(若存在)

  2. 全局Entrance_Node節點(若當前節點類型爲EntryType.IN);執行後將調用onPass回調函數。      其餘狀況如圖示,包括:

  3. 在獲取token設置了優先策略,等待超時拋出PriorityWaitExeption(注:爲何只增長ThreadNum但不增長PassRequest,卻執行了onPass回調函數?這裏PriorityWatitException並非BlockException,拋出PriorityWaitException時,該請求已經獲取了令牌,能夠執行後續的操做,只是不在當前窗口,這點後續會說明)

  4. 後續Slot規則不經過,拋出BlockException

  5. 發生其餘異常,這時候會設置當前節點的Error值,爲exit動做作判斷

  退出的動做以下,該行爲發生在fireExit前,用於統計成功時的響應時間,減去獲取token時的線程數,增長成功請求數(SuccessRequest)

file

  具體的統計方法,後續會對StatisticNode作說明。

3.5.SystemSlot

  經過系統的狀態,例如 load1 等,來控制總的入口流量;

  檢查當前系統指標是否正常,只檢查入口流量節點。包括全局QPS,全局線程數,全局平均響應時間,系統負載,CPU負載。

3.6.AuthoritySlot

  根據配置的黑白名單和調用來源信息,來作黑白名單控制;

3.7.FlowSlot

  用於根據預設的限流規則以及前面 slot 統計的狀態,來進行流量控制;

file

  首先會根據規則設定的模式,選擇處理方式,有Local和Cluster兩種,這裏先介紹Local方式。

  Local方式時,先選擇統計數據的節點,再根據設定的限流器獲取token,達到限流的目的。

3.7.1 選擇統計數據的節點

  這一步將根據給定設置的應用範圍,和限流策略來選擇對應的節點。

  這邊先介紹默認的應用範圍和限流策略,分爲:

  1. 應用範圍:default,other

  2. 限流策略:DIRECT,RELATE,CHAIN

  選擇時,將根據調用方LimitApp來選擇對應的節點。若規則做用於origin上且除default和other外,若是是DIRECT策略,返回origin節點;如果做用於default上,若是是DIRECT策略,返回Cluster節點;如果做用於other上,若是是DIRECT策略,返回origin節點。上述其餘狀況的選擇過程都是相同的,即:若是資源名爲空,返回空;若是是RELATE策略,使用ClusterNode節點數據;若是是CHIAN策略,且當前節點名同規則名一致,使用當前節點數據,不然返回爲空,詳情能夠看代碼。

  得到數據節點後,即可以使用規則中指定的限流器校驗節點的數據,以獲取token。

3.7.2 根據限流器獲取token

  系統提供的限流器包括:

  1. 默認限流器:直接拒絕

    默認限流器採用直接拒絕的方式,若當前已經被獲取的token數和須要的token數大於設定的規則,則直接拒絕,支持按線程數和QPS來算。當按QPS算時,還支持優先模式,容許參照以前的使用狀況,在必定指望時間內,從後續時間借用令牌,保證當前請求可以經過。支持優先模式可以充分利用系統的資源,儘量多的接受請求,防止請求被沒必要要的攔截

  2. 預熱限流器:參考guava,提供帶預熱/冷啓動功能的令牌桶方法

    參考guava的預熱限流器,按照請求數來計算。Token個數不是一開始就達到設定的上限,而是有個預熱的過程,在預熱的時間內,token的生成速度是固定的,當超過該時間後將根據可用token數,調整速率,使之增長到設定的值。這樣作可以有效的防止突發流量穿透到後臺服務。

  3. 恆定速率限流器:根據預設的速率進行恆定控制

    根據設定的QPS,能夠獲得每一個token所需的恆定時間,對於所需的token數,可以預估所需的時間。若該等待時間大於最大等待時間,則拒絕,不然更新上次經過時間(該規則在上個請求已經獲取token後的預期時間),加上該次須要的時間,並讓該次請求進行等待。可以保證token的獲取速率是平滑恆定的,達到削峯填谷,防止經過的請求扎堆在窗口的前段。

  4. 預熱的恆定速率限流器:前期同預熱限流器相同,過了預熱時間後,將按照恆定的速率獲取token。

3.8.DegradeSlot

  則經過統計信息以及預設的規則,來作熔斷降級;

file

  降級的流程如上:

  1. 流程進來時,若是該規則(資源)已經處於降級狀態,則直接返回校驗不經過

  2. 若不處於降級狀態,則獲取該資源的ClusterNode節點數據,按照設定的降級類型進行判斷:

    1. 根據響應時間判斷:若該資源的平均響應時間小於規則的設定值,則重置不經過的次數爲0,並返回true;如不經過的次數小於默認值,則返回true;不然進入降級流程

    2. 根據異常率判斷:若該資源異常次數小於默認值或者異常率小於設定值,則返回true;不然進入降級流程

    3. 根據異常次數判斷:若異常次數小於設定值,則返回true;不然進入降級流程

  3. 降級流程:將資源狀態置爲降級,並開啓定時器,該定時器將在規則設定的時間後執行,執行時將重置該規則指定的資源降級狀態

4.Node

4.1 滑動窗口模型

  Sentinel 底層採用高性能的滑動窗口數據結構 LeapArray 來統計實時的秒級指標數據,能夠很好地支撐寫多於讀的高併發場景。

  LeapArray主要包括以下屬性 :

file

  滑動窗口模型以下:

file

  LeapArray的主體實現以下(去除原有備註,換上本身的備註):  

file

file

  將array抽象爲一個環,則上面的流程能夠按以下圖看:

file

  主體流程爲:

  1. 計算所給時間所在窗口索引號:i

  2. 計算所給時間所在窗口開始時間:ws

  3. 根據索引號i獲取現有窗口對象:window

  4. 根據現有窗口對象:window,判斷是否須要更新當前窗口

    1. 若是現有窗口對象爲空,則初始化當前窗口

    2. 若是現有窗口的開始時間同ws一致,則說明現有窗口還未過時,繼續使用當前窗口

    3. 若是現有窗口的開始時間小於ws,說明現有窗口已通過期,須要更新該窗口

4.2 StatisticNode

  StatisticNode爲Sentinel實現監控統計的必要組件,該組件可以實現不一樣粒度,不一樣維度的數據監控統計。

file

  上圖爲Statistic必要組件的組成:

  1. LeapArray:滑動窗口模型,實現不一樣時間粒度的滑動窗口行爲,LeapArray爲抽象類

  2. BucketLeapArray:LeapArray的實現類,主要實現方法

    1. newEmptyBucket:初始化窗口的動做,這裏初始化時設置了包裝類MetricBucket

    2. resetWindowTo:更新窗口的動做,這裏重置了窗口的開始時間,以及重置了窗口中包裝類的值

  3. MetricBucket:存儲着各個統計維度的計數,統計的維度爲MetricEvent枚舉值

  4. MetricEvent:統計維度,包括

    1. PASS:得到令牌的計數

    2. BLOCK:未得到令牌的計數

    3. EXCEPTION:發生異常的計算

    4. SUCCESS:得到令牌併成功歸還的計數

    5. RT:請求時間

  5. Metric:統計接口,按接口譯,能夠理解爲「可統計的」,聚合了採集統計信息以及生成統計信息的方法。採集統計信息爲各個維度的add方法,生成統計信息使用windows和details方法,返回MetricNode包含各統計信息

    1. windows:返回如今LeapArray中的統計信息,以MetricBucket形式

    2. details:返回如今LeapArray中的統計信息,以MetricNode的形式

  6. MetricNode:Bean,各屬性爲統計信息的值,至關於當前系統的一個鏡像數據,將計數數據按照維度進行運算過。

  7. ArrayMetric:Metric的實現類,內部使用LeapArray做爲滑動窗口統計各個窗口的數據

  8. StatisticNode:統計節點,內部使用1秒和1分鐘的滑動窗口來統計數據,同時還會記錄當前的線程數

    1. rollingCounterInSecond:sampleCounter爲2,intervalInMs爲1秒的滑動窗口

    2. rollingCounterInMinute:sampleCounter爲60,intervalInMs爲1分鐘的滑動窗口

  9. ClusterNode : 繼承自StatisticNode,對於某一個資源的全局統計

  10. DefaultNode:繼承自StatisticNode,對於某一個資源在相應上下文中的實現,保存了一個指向ClusterNode的引用。另外還保存了子節點列表,當在同一個context下屢次調用SphU.entry不一樣資源時會建立子節點

  主要調用流程以下:

file

  流程到最後,將累加MetricBucket中維護的各維度計數數據。這些數據能夠在調用時轉爲MetricNode提供格式化數據。

4.3 LeapArray

  LeapArray的實現包括:

file

  1. BucketLeapArray:每一個窗口持有一個MetricBucket,該對象存儲着當前窗口內各個維度的計數值

  2. FutureLeapArray:只存儲比當前時間大的窗口

  3. BorrowBucketLeapArray:持有FutureLeapArray,支持從後續窗口中借用資源

  4. LeapArray的默認實現上,每一個窗口在intervalInMs內都是有效的

file

  如圖,在某個時間800時,intervalInMs內的各個窗口都是有效的,這時候計算qps將使用各個窗口的統計值之和。

  FutureLeapArray重寫了isWindowDeprecated方法,以下

file

  只要給定時間大於給定窗口的起始時間則算窗口失效;當給定時間爲當前時間,窗口爲上一個窗口或者當前時間所在窗口時,都是失效的,只能存給定時間後的窗口。上圖中,在給定的時間爲800時,只有1000這個窗口是有效的。

  BorrowBucketLeapArray,主要用於在進行限流時支持有限模式,噹噹前的token不夠時,容許先從後續窗口中獲取token。內部持有一個FutureLeapArray,該窗口隊列用於存儲在當前時間後的窗口。經過重寫LeapArray的addWaiting方法,佔用指定的後續窗口計數值,再經過currentWaiting方法,能夠獲取當前時間已經佔用後後續窗口多少個資源。

file

  由於borrowArray中的窗口可能在以前已經初始化或者使用過,於是,BorrowBucketLeapArray在初始化窗口或者更新窗口時,會考慮borrowArray中已有的窗口數據,以下重寫的newEmptyBucket方法和resetWindowTo方法。

file

  newEmptyBucket方法初始化時,若是發現該時間所在的窗口已經在FutureBucketArray中出現過,將會使用該窗口的值。同理,restWindowTo在更新時,若是所給時間窗口已經存在,則加上以前已經存在的計數值。

  StatisticNode在使用時還會根據以前統計的請求,估算後續窗口的可用請求,再從後續窗口借用token,具體實現以下:

file

  執行時會先計算離當前時間最遠的一個有效窗口的開始值,以下圖,假設當前窗口爲curr,則earliestTime落在earliest 1的開始處。而後從earliest 1開始,逐次增長一個窗口,以逐步根據以前的Pass值,估算後續可能出現的請求,若是根據以前的某個窗口的值估算出後續某個窗口存在空閒的token,且等待時間在指望的時間內,則hold住當前請求,使之到達特定窗口後再繼續經過。下圖假設設定規則的最大QPS爲20,在前3個窗口時,每次都沒有達到最大限定的最大值,則能夠認爲,在curr值後的一個窗口內也是該種狀況。當curr忽然到來22個請求時,根據規則將有2個請求被拒接掉,但根據以前窗口的狀況,這兩個請求能夠在後續的第二個請求中完成,以此充分的利用系統的資源。

file

5.RuleManager

  各規則管理的模式都一致,主要用到以下3個組件

  1. RuleManager,具體的規則實現類,沒有具體的實現接口,可是都有loadRules方法

  2. ProertyListener,規則監聽器

  3. SentinelProperty,觀察者,持有各監聽器

  RuleManaer內部持有PropertyListener和SentinelProerty,而且RuleManager有PropertyListener的內部實現類

  具體流程爲,初始化時RuleManager調用SentinelProepety.addListener,設置監聽器。SentinelProperty會調用PropertyListener.configLoad,完成初始化。後面調用RuleManager.loadRules從新更改規則時,內部調用者SentinelProerty.updateValue,該方法會遍歷SentinelProperty內部持有的全部Listener,逐個執行PropertyListner.configUpdate,從而通知到RuleManager規則發生了改變,以便讓RuleManager作出處理。

file

file

我的公衆號:啊駝

相關文章
相關標籤/搜索