Sentinel中有很多比較重要的概念,我們要了解一個框架,首先要對框架中重要的概念實體進行分析,本文我將跟大家一起來分析一下sentinel中非常重要的幾個概念。
Resource
resource是sentinel中最重要的一個概念,sentinel通過資源來保護具體的業務代碼或其他后方服務。sentinel把復雜的邏輯給屏蔽掉了,用戶只需要為受保護的代碼或服務定義一個資源,然后定義規則就可以了,剩下的通通交給sentinel來處理了。并且資源和規則是解耦的,規則甚至可以在運行時動態修改。定義完資源后,就可以通過在程序中埋點來保護你自己的服務了,埋點的方式有兩種:
- try-catch 方式(通過
SphU.entry(...)
),當 catch 到BlockException時執行異常處理(或fallback) - if-else 方式(通過
SphO.entry(...)
),當返回 false 時執行異常處理(或fallback)
以上這兩種方式都是通過硬編碼的形式定義資源然后進行資源埋點的,對業務代碼的侵入太大,從0.1.1版本開始,sentinel加入了注解的支持,可以通過注解來定義資源,具體的注解為:SentinelResource
。通過注解除了可以定義資源外,還可以指定 blockHandler
和 fallback
方法。
在sentinel中具體表示資源的類是:ResourceWrapper
,他是一個抽象的包裝類,包裝了資源的 Name 和EntryType。他有兩個實現類,分別是:StringResourceWrapper
和 MethodResourceWrapper
顧名思義,StringResourceWrapper
是通過對一串字符串進行包裝,是一個通用的資源包裝類,MethodResourceWrapper
是對方法調用的包裝。
Slot
slot是另一個sentinel中非常重要的概念,sentinel的工作流程就是圍繞著一個個插槽所組成的插槽鏈來展開的。需要注意的是每個插槽都有自己的職責,他們各司其職完好的配合,通過一定的編排順序,來達到最終的限流降級的目的。默認的各個插槽之間的順序是固定的,因為有的插槽需要依賴其他的插槽計算出來的結果才能進行工作。
但是這并不意味著我們只能按照框架的定義來,sentinel 通過 SlotChainBuilder
作為 SPI 接口,使得 Slot Chain 具備了擴展的能力。我們可以通過實現 SlotsChainBuilder
接口加入自定義的 slot 并自定義編排各個 slot 之間的順序,從而可以給 sentinel 添加自定義的功能。
那SlotChain是在哪創建的呢?是在 CtSph.lookProcessChain()
方法中創建的,并且該方法會根據當前請求的資源先去一個靜態的HashMap中獲取,如果獲取不到才會創建,創建后會保存到HashMap中。這就意味著,同一個資源會全局共享一個SlotChain
Context
context上下文是sentinel中一個比較難懂的概念。源碼中是這樣描述context類的:
This class holds metadata of current invocation
就是說在context中維護著當前調用鏈的元數據,那元數據有哪些呢,從context類的源碼中可以看到有:
- entranceNode:當前調用鏈的入口節點
- curEntry:當前調用鏈的當前entry
- node:與當前entry所對應的curNode
- origin:當前調用鏈的調用源
每次調用 SphU.entry()
或 SphO.entry()
都需要在一個 context 中執行,如果沒有當前執行時還沒有 context,那么框架會使用默認的 context,默認的 context 是通過 MyContextUtil.myEnter()
創建的。
那如果我想自己在調用 SphU.entry()
或 SphO.entry()
前,自己創建一個context該怎么操作呢?那可以通過調用 ContextUtil.enter()
方法來創建。
另外context是保存在ThreadLocal中的,每次執行的時候會優先到ThreadLocal中獲取。如果context為null時才會再次去創建一個context。
那什么時候context會被置為null并從ThreadLocal中清空呢?當Entry執行exit方法時,當當前entry的parent為null時,也就說明當前entry是最上層的節點了,此時要把保存在ThreadLocal中的context也清空掉。
在NodeSelectorSlot類中有一個Map保存了DefaultNode,但是key是用的contextName,而不是resourceName,這是為什么呢?
試想一下,如果用resourceName來做map的key,那對于同一個資源resourceA來說,在context1中獲取到的defaultNodeA和在context2中獲取到的defaultNodeA是同一個,那么怎么在這兩個context中對defaultNodeA進行更改呢,修改了一個必定會對另一個產生影響。
而如果用contextName來作為key,那對于同一個資源resourceA來說,在context1中獲取到的是defaultNodeA1,在context2中獲取到是defaultNodeA2,那在不同的context中對同一個資源可以使用不同的DefaultNode進行分別統計和計算,最后再通過ClusterNode進行合并就可以了。
所以在NodeSelectorSlot這個類里面,map里面保存的是contextName和DefaultNode的映射關系,目的是為了可以在不同的context對相同的資源進行分開統計。
同一個context中對同一個resource進行多次entry()調用時,會形式一顆調用樹,這個樹是通過CtEntry之間的parent/child關系維護的。
具體的調用鏈的原理分析可以參考筆者的另一篇文章:限流降級神器-哨兵(sentinel)的資源調用鏈原理分析
Entry
entry是sentinel中用來表示是否通過限流的一個憑證,就像一個token一樣。每次執行 SphU.entry()
或 SphO.entry()
都會返回一個 Entry
給調用者,意思就是告訴調用者,如果正確返回了 Entry
給你,那表示你可以正常訪問被sentinel保護的后方服務了,否則sentinel會拋出一個BlockException(如果是 SphO.entry()
會返回false),這就表示調用者想要訪問的服務被保護了,也就是說調用者本身被限流了。
entry中保存了本次執行 entry() 方法的一些基本信息,包括:
- createTime:當前Entry的創建時間,主要用來后期計算rt
- node:當前Entry所關聯的node,該node主要是記錄了當前context下該資源的統計信息
- origin:當前Entry的調用來源,通常是調用方的應用名稱,在
ClusterBuilderSlot.entry()
方法中設置的 - resourceWrapper:當前Entry所關聯的資源
當在一個上下文中多次調用了 SphU.entry()
方法時,就會創建一個調用樹,這個樹的節點之間是通過parent和child關系維持的。
需要注意的是:parent和child是在 CtSph
類的一個私有內部類 CtEntry
中定義的,CtEntry
是 Entry
的一個子類。
由于context中總是保存著調用鏈樹中的當前入口,所以當當前entry執行exit退出時,需要將parent設置為當前入口。
Node
node中保存了資源的實時統計數據,例如:passQps,blockQps,rt等實時數據。正是有了這些統計數據后,sentinel才能進行限流、降級等一系列的操作。
node是一個接口,他有一個實現類:StatisticNode,但是StatisticNode本身也有兩個子類,一個是DefaultNode,另一個是ClusterNode,DefaultNode又有一個子類叫EntranceNode。
其中entranceNode是每個上下文的入口,該節點是直接掛在root下的,是全局唯一的,每一個context都會對應一個entranceNode。另外defaultNode是記錄當前調用的實時數據的,每個defaultNode都關聯著一個資源和clusterNode,有著相同資源的defaultNode,他們關聯著同一個clusterNode。
Metric
metric是sentinel中用來進行實時數據統計的度量接口,node就是通過metric來進行數據統計的。而metric本身也并沒有統計的能力,他也是通過Window來進行統計的。
具體的統計原理,可以參考筆者另一篇文章:sentinel基于滑動時間窗口的實時指標統計原理分析
Metric有一個實現類:ArrayMetric,在ArrayMetric中主要是通過一個叫WindowLeapArray的對象進行窗口統計的。