限流降級神器哨兵 (sentinel) 的資源呼叫鏈原理分析
我們已經知道了sentinel實現限流降級的原理,其核心就是一堆Slot組成的呼叫鏈。
這裡大概的介紹下每種Slot的功能職責:
-
NodeSelectorSlot
負責收集資源的路徑,並將這些資源的呼叫路徑,以樹狀結構儲存起來,用於根據呼叫路徑來限流降級; -
ClusterBuilderSlot
則用於儲存資源的統計資訊以及呼叫者資訊,例如該資源的 RT, QPS, thread count 等等,這些資訊將用作為多維度限流,降級的依據; -
StatisticsSlot
則用於記錄,統計不同維度的 runtime 資訊; -
SystemSlot
則通過系統的狀態,例如 load1 等,來控制總的入口流量; -
AuthoritySlot
則根據黑白名單,來做黑白名單控制; -
FlowSlot
則用於根據預設的限流規則,以及前面 slot 統計的狀態,來進行限流; -
DegradeSlot
則通過統計資訊,以及預設的規則,來做熔斷降級;
每個Slot執行完業務邏輯處理後,會呼叫fireEntry()方法,該方法將會觸發下一個節點的entry方法,下一個節點又會呼叫他的fireEntry,以此類推直到最後一個Slot,由此就形成了sentinel的責任鏈。
下面我們就來詳細研究下這些Slot的原理。
NodeSelectorSlot
NodeSelectorSlot
是用來構造呼叫鏈的,具體的是將資源的呼叫路徑,封裝成一個一個的節點,再組成一個樹狀的結構來形成一個完整的呼叫鏈, NodeSelectorSlot
是所有Slot中最關鍵也是最複雜的一個Slot,這裡涉及到以下幾個核心的概念:
-
Resource
資源是 Sentinel 的關鍵概念。它可以是 Java 應用程式中的任何內容,例如,由應用程式提供的服務,或由應用程式呼叫的其它服務,甚至可以是一段程式碼。
只要通過 Sentinel API 定義的程式碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。
簡單來說,資源就是 Sentinel 用來保護系統的一個媒介。原始碼中用來包裝資源的類是: com.alibaba.csp.sentinel.slotchain.ResourceWrapper
,他有兩個子類: StringResourceWrapper
和 MethodResourceWrapper
,通過名字就知道可以將一段字串或一個方法包裝為一個資源。
打個比方,我有一個服務A,請求非常多,經常會被陡增的流量沖垮,為了防止這種情況,簡單的做法,我們可以定義一個 Sentinel 的資源,通過該資源來對請求進行調整,使得允許通過的請求不會把服務A搞崩潰。
每個資源的狀態也是不同的,這取決於資源後端的服務,有的資源可能比較穩定,有的資源可能不太穩定。那麼在整個呼叫鏈中,Sentinel 需要對不穩定資源進行控制。當呼叫鏈路中某個資源出現不穩定,例如,表現為 timeout,或者異常比例升高的時候,則對這個資源的呼叫進行限制,並讓請求快速失敗,避免影響到其它的資源,最終導致雪崩的後果。
-
Context
上下文是一個用來儲存呼叫鏈當前狀態的元資料的類,每次進入一個資源時,就會建立一個上下文。 並且相同的資源名只會建立一個上下文。 一個Context中包含了三個核心的物件:
1)當前呼叫鏈的根節點:EntranceNode
2)當前的入口:Entry
3)當前入口所關聯的節點:Node
上下文中只會儲存一個當前正在處理的入口Entry,另外還會儲存呼叫鏈的根節點。 需要注意的是,每次進入一個新的資源時,都會建立一個新的上下文。
-
Entry
每次呼叫 SphU#entry()
都會生成一個Entry入口,該入口中會儲存了以下資料:入口的建立時間,當前入口所關聯的節點,當前入口所關聯的呼叫源對應的節點。Entry是一個抽象類,他只有一個實現類,在CtSph中的一個靜態類:CtEntry
-
Node
節點是用來儲存某個資源的各種實時統計資訊的,他是一個介面,通過訪問節點,就可以獲取到對應資源的實時狀態,以此為依據進行限流和降級操作。
可能看到這裡,大家還是比較懵,這麼多類到底有什麼用,接下來就讓我們更進一步,挖掘一下這些類的作用,在這之前,我先給大家展示一下他們之間的關係,如下圖所示:
這裡把幾種Node的作用先大概介紹下:
節點 | 作用 |
---|---|
StatisticNode | 執行具體的資源統計操作 |
DefaultNode | 該節點持有指定上下文中指定資源的統計資訊,當在同一個上下文中多次呼叫entry方法時,該節點可能下會建立有一系列的子節點。 另外每個DefaultNode中會關聯一個ClusterNode |
ClusterNode | 該節點中儲存了資源的總體的執行時統計資訊,包括rt,執行緒數,qps等等,相同的資源會全域性共享同一個ClusterNode,不管他屬於哪個上下文 |
EntranceNode | 該節點表示一棵呼叫鏈樹的入口節點,通過他可以獲取呼叫鏈樹中所有的子節點 |
Context的建立與銷燬
首先我們要清楚的一點就是,每次執行entry()方法,試圖衝破一個資源時,都會生成一個上下文。這個上下文中會儲存著呼叫鏈的根節點和當前的入口。
Context是通過ContextUtil建立的,具體的方法是trueEntry,程式碼如下:
上面的程式碼中我省略了部分程式碼,只保留了核心的部分。從原始碼中還是可以比較清晰的看出生成Context的過程:
-
1.先從ThreadLocal中獲取,如果能獲取到直接返回,如果獲取不到則繼續第2步
-
2.從一個static的map中根據上下文的名稱獲取,如果能獲取到則直接返回,否則繼續第3步
-
3.加鎖後進行一次double check,如果還是沒能從map中獲取到,則建立一個EntranceNode,並把該EntranceNode新增到一個全域性的ROOT節點中去,然後將該節點新增到map中去(這部分程式碼在上述程式碼中省略了)
-
4.根據EntranceNode建立一個上下文,並將該上下文儲存到ThreadLocal中去,下一個請求可以直接獲取
那儲存在ThreadLocal中的上下文什麼時候會清除呢?從程式碼中可以看到具體的清除工作在ContextUtil的exit方法中,當執行該方法時,會將儲存在ThreadLocal中的context物件清除,具體的程式碼非常簡單,這裡就不貼程式碼了。
那ContextUtil.exit方法什麼時候會被呼叫呢?有兩種情況:一是主動呼叫ContextUtil.exit的時候,二是當一個入口Entry要退出,執行該Entry的trueExit方法的時候,此時會觸發ContextUtil.exit的方法。但是有一個前提,就是當前Entry的父Entry為null時,此時說明該Entry已經是最頂層的根節點了,可以清除context。
呼叫鏈樹
當在一個上下文中多次呼叫了 SphU#entry() 方法時,就會建立一棵呼叫鏈樹。具體的程式碼在entry方法中建立CtEntry物件時:
這裡可能看程式碼沒有那麼直觀,可以用一些圖形來描述一下這個過程。
構造樹幹
建立context
context的建立在上面已經分析過了,初始化的時候,context中的curEntry屬性是沒有值的,如下圖所示:
建立Entry
每建立一個新的Entry物件時,都會重新設定context的curEntry,並將context原來的curEntry設定為該新Entry物件的父節點,如下圖所示:
退出Entry
某個Entry退出時,將會重新設定context的curEntry,當該Entry是最頂層的一個入口時,將會把ThreadLocal中儲存的context也清除掉,如下圖所示:
構造葉子節點
上面的過程是構造了一棵呼叫鏈的樹,但是這棵樹只有樹幹,沒有葉子,那葉子節點是在什麼時候建立的呢?DefaultNode就是葉子節點,在葉子節點中儲存著目標資源在當前狀態下的統計資訊。通過分析,我們知道了葉子節點是在NodeSelectorSlot的entry方法中建立的。具體的程式碼如下:
上面的程式碼可以分解成下面這些步驟: 1)獲取當前上下文對應的DefaultNode,如果沒有的話會為當前的呼叫新生成一個DefaultNode節點,它的作用是對資源進行各種統計度量以便進行流控; 2)將新建立的DefaultNode節點,新增到context中,作為「entranceNode」或者「curEntry.parent.curNode」的子節點; 3)將DefaultNode節點,新增到context中,作為「curEntry」的curNode。
上面的第2步,不是每次都會執行。我們先看第3步,把當前DefaultNode設定為context的curNode,實際上是把當前節點賦值給context中curEntry的curNode,用圖形表示就是這樣:
多次建立不同的Entry,並且執行NodeSelectorSlot的entry方法後,就會變成這樣一棵呼叫鏈樹:
PS:這裡圖中的node0,node1,node2可能是相同的node,因為在同一個context中從map中獲取的node是同一個,這裡只是為了表述的更清楚所以用了不同的節點名。
儲存子節點
上面已經分析了葉子節點的構造過程,葉子節點是儲存在各個Entry的curNode屬性中的。
我們知道context中只儲存了入口節點和當前Entry,那子節點是什麼時候儲存的呢,其實子節點就是上面程式碼中的第2步中儲存的。
下面我們來分析上面的第2步的情況:
第一次呼叫NodeSelectorSlot的entry方法時,map中肯定是沒有DefaultNode的,那就會進入第2步中,建立一個node,建立完成後會把該節點加入到context的lastNode的子節點中去。我們先看一下context的getLastNode方法:
程式碼中我們可以知道,lastNode的值可能是context中的entranceNode也可能是curEntry.parent.curNode,但是他們都是「DefaultNode」型別的節點,DefaultNode的所有子節點是儲存在一個HashSet中的。
第一次呼叫getLastNode方法時,context中curEntry是null,因為curEntry是在第3步中才賦值的。所以,lastNode最初的值就是context的entranceNode。那麼將node新增到entranceNode的子節點中去之後就變成了下面這樣:
緊接著再進入一次,資源名不同,會再次生成一個新的Entry,上面的圖形就變成下圖這樣:
此時再次呼叫context的getLastNode方法,因為此時curEntry的parent不再是null了,所以獲取到的lastNode是curEntry.parent.curNode,在上圖中可以很方便的看出,這個節點就是 node0 。那麼把當前節點node1新增到lastNode的子節點中去,上面的圖形就變成下圖這樣:
然後將當前node設定給context的curNode,上面的圖形就變成下圖這樣:
假如再建立一個Entry,然後再進入一次不同的資源名,上面的圖就變成下面這樣:
至此NodeSelectorSlot的基本功能已經大致分析清楚了。
PS:以上的分析是基於每次執行SphU.entry(name)時,資源名都是不一樣的前提下。如果資源名都一樣的話,那麼生成的node都相同,則只會再第一次把node加入到entranceNode的子節點中去,其他的時候,只會建立一個新的Entry,然後替換context中的curEntry的值。
ClusterBuilderSlot
NodeSelectorSlot的entry方法執行完之後,會呼叫fireEntry方法,此時會觸發ClusterBuilderSlot的entry方法。
ClusterBuilderSlot的entry方法比較簡單,具體程式碼如下:
NodeSelectorSlot的職責比較簡單,主要做了兩件事:
一、為每個資源建立一個clusterNode,然後把clusterNode塞到DefaultNode中去
二、將clusterNode保持到全域性的map中去,用資源作為map的key
PS:一個資源只有一個ClusterNode,但是可以有多個DefaultNode
StatistcSlot
StatisticSlot負責來統計資源的實時狀態,具體的程式碼如下:
程式碼分成了兩部分,第一部分是entry方法,該方法首先會觸發後續slot的entry方法,即SystemSlot、FlowSlot、DegradeSlot等的規則,如果規則不通過,就會丟擲BlockException,則會在node中統計被block的數量。反之會在node中統計通過的請求數和執行緒數等資訊。第二部分是在exit方法中,當退出該Entry入口時,會統計rt的時間,並減少執行緒數。
這些統計的實時資料會被後續的校驗規則所使用,具體的統計方式是通過 滑動視窗
來實現的。後面我會詳細分析滑動視窗的原理。
SystemSlot
SystemSlot就是根據總的請求統計資訊,來做流控,主要是防止系統被搞垮,具體的程式碼如下:
其中的Constants.ENTRY_NODE是一個全域性的ClusterNode,該節點的值是在StatisticsSlot中進行統計的。
AuthoritySlot
AuthoritySlot做的事也比較簡單,主要是根據黑白名單進行過濾,只要有一條規則校驗不通過,就丟擲異常。
FlowSlot
FlowSlot主要是根據前面統計好的資訊,與設定的限流規則進行匹配校驗,如果規則校驗不通過則進行限流,具體的程式碼如下:
DegradeSlot
DegradeSlot主要是根據前面統計好的資訊,與設定的降級規則進行匹配校驗,如果規則校驗不通過則進行降級,具體的程式碼如下:
總結
sentinel的限流降級等功能,主要是通過一個SlotChain實現的。在鏈式插槽中,有7個核心的Slot,這些Slot各司其職,可以分為以下幾種型別:
一、進行資源呼叫路徑構造的NodeSelectorSlot和ClusterBuilderSlot
二、進行資源的實時狀態統計的StatisticsSlot
三、進行系統保護,限流,降級等規則校驗的SystemSlot、AuthoritySlot、FlowSlot、DegradeSlot
後面幾個Slot依賴於前面幾個Slot統計的結果。至此,每種Slot的功能已經基本分析清楚了。
我是逅弈,白天是西廠的一名攻城獅,晚上是原創知識分享者。如果我的文章對您有幫助,歡迎您點贊加關注