Sentinel Cluster流程分析

  • 2019 年 10 月 3 日
  • 筆記

 前面介紹了sentinel-core的流程,提到在進行流控判斷時,會判斷當前是本地限流,還是集群限流,若是集群模式,則會走另一個分支,這節便對集群模式做分析。

一.基本概念

 namespace:限流作用於,用於區分一個規則作用於什麼範圍

 flowId:代表全局唯一的規則 ID,Sentinel 集群限流服務端通過此 ID 來區分各個規則,因此務必保持全局唯一。一般 flowId 由統一的管控端進行分配,或寫入至 DB 時生成。

 thresholdType:代表集群限流閾值模式。其中單機均攤模式下配置的閾值等同於單機能夠承受的限額,token server 會根據客戶端對應的 namespace(默認為 project.name 定義的應用名)下的連接數來計算總的閾值(比如獨立模式下有 3 個 client 連接到了 token server,然後配的單機均攤閾值為 10,則計算出的集群總量就為 30);而全局模式下配置的閾值等同於整個集群的總閾值。

二.通訊框架

 sentinel-cluster基於netty提供了一套遠程通訊框架,分為客戶端和服務,其使用了jdk自帶的SPI,提供了一些介面的默認實現。如下圖為sentinel-cluster-client客戶端模組的默認實現類。

file

 InitFunc的載入是通過InitExecutor載入的,InitExecutor在sentinel-core模組中。InitExecutor會在全局訪問內載入所有InitFunc的實現類,並調用其init方法完成初始化。該模組中配置的InitFunc實現類為DefaultClusterClientInitFunc,該類會初始化通訊協議中各種類型的編碼和解碼處理類。編解碼器將調用註冊工廠RequestDataWriterRegistry和ResponseDataDecodeRegistry的方法進行註冊,供後續使用。系統提供了PING,FLOW(流控)和PARAM_FLOW(熱點參數流控)三種編解碼器。

file

file

 上圖為sentinel-cluster的通訊協議格式,請求和響應中有個4個位元組的消息id和1個位元組的消息類型,剩下的就是消息體,對於響應格式,有1個位元組的狀態資訊。需要說明的是,在初始化Netty客戶端時,增加了兩個filter:

file

 也就是說在發送一個消息時,會自動加上長度為2個位元組的消息長度頭部,在讀取時也會自動省略2個位元組的消息長度頭部。
為了解析上面的消息格式,在提供了註冊方法之上,sentinel還提供了ClientEntityCodeProvider,統一了報文的處理。

file

 如上,該類在static靜態程式碼塊中進行了初始化,使用SPI,獲取RequestEntityWriter和ResponseEntityDecoder的實現類,這兩種實現類也在該模組中指定了默認實現:DefaultResponseEntityDecoder和DefaultRequestEntityWriter。即處理過程為

  ClientEntityCodecProvider->ResponseEntityDecoder->ResponseDataDecodeRegisty-> EntityDecoder    ClientEntityCodecProvider->RequestEntityWriter->RequestDataWriterRegisty-> EntityWriter  

 系統還提供了TokenClientHandler類,用於響應數據流,進行相應的處理

file

 如上只列出了比較重要的屬性和方法。該類繼承了ChannelInboundHandlerAdapter並實現了對應的方法,currentState屬性用於標記客戶端當前的狀態,disconnectCallback則用於負責在斷線時進行重連。TokenClientHandler實現channelActive方法,會在連接建立時會發送PING請求給服務端;實現channelUnregistered方法,會在連接斷開時調用disconnectCallback,在一定時間後進行重連,等待時間跟失敗次數有關;實現channelRead方法,會在有響應數據時,接收響應內容,並進行處理,處理流程如下:

file

 在經過Netty處理解析為消息類型對象後,會判斷該響應的類型,如果是PING消息的響應,則直接輸出日誌,否則將從TokenClientPromiseHolder中根據消息id設置對應的響應內容,以便消息發送執行緒能夠獲得響應。
 上面提到的TokenClientPromiseHolder用於快取請求消息。如下圖,發送消息後,會獲取對應的ChannelPromise對象,並根據消息存於TokenClientPromiseHolder中。ChannelPromise會等待Netty請求響應回來,對應的流程如上面InBound流程。在請求正常響應後,會根據消息id再從TokenClientPromiseHolder中獲取對應的響應結果。

file

file

 Cluster模組的核心介面為TokenService ,ClusterTokenServer和ClusterTokenClient。其中ClusterTokenClient內部主要類為NettyTransportClient,在上面已經進行了說明,下面說下其他兩個介面。TokenService ,ClusterTokenServer在模組中的關係如下圖:

file

 其中介面都由SPI給出了默認的實現,如下:

file

 下面對涉及到的介面和類進行說明。

 TokenService:token服務介面,提供了requestToken和requestParamToken方法,分別表示獲取流控令牌和獲取熱點參數令牌。提供的默認實現為DefaultTokenService,會在TokenServiceProvider初始化時使用SPI進行載入。

 ClusterTokenServer:服務端上層介面,提供了start和stop方法用於服務端的啟動和停止。

 NettyTransportServer:ClusterTokenServer的netty實現,同客戶端對應,有如下的pipeline配置

file

 其中編解碼器的處理同客戶端類似,只是增加了服務端的處理器:TokenServerHandler。TokenServerHandler繼承自ChannelInboundHandlerAdapter用以在連接建立和有數據交互時進行相應的處理:

  1. 實現channelActive:在連接建立時將其快取起來

  2. 實現channelInactive:在連接斷開時移除快取

  3. 實現channelRead:在有數據到來時,進行處理。這裡會使用RequestProcessorProvider載入的RequestProcessor實現類,根據請求的類型(type欄位)選擇相應的處理類進行處理。系統現在提供的處理類有FlowRequestProcessor和ParamFlowRequestProcessor,這兩者最後都將通過TokenServiceProvider獲得DefaultTokenService對象,調用其來完成請求。

 SentinelDefaultTokenServer:包裝了NettyTransportServer方法,增加了ServerTransportConfigObserver用於監聽服務端配置項的更改,從而更新自身。

 EmbeddedClusterTokenServer:繼承自TokenService和ClusterTokenServer,用於內嵌服務端模式,默認實現為DefaultEmbeddedClusterTokenServer。

 DefaultEmbeddedClusterTokenServer:主要組合了DefaultTokenService和SentinelDefaultTokenServer對象用以實現介面方法。

 結合上面服務端的實現,可以得到客戶端請求一個token的流程如下:

file

  1. 客戶端調用DefaultClusterTokenClient的requestToken方法獲取token,其內部會委託NettyTransportClient編碼後發給服務端
  2. 服務端NettyTransportServer收到請求後,由TokenServerHandler的channelRead方法處理這裡會根據請求內容中的type,委託給對應的消息處理處理,如FlowRequestProcessor
  3. FlowRequestProcessor會調用TokenServiceProvider獲取對應的TokenService實現類,默認為DefaultTokenService。然後委託為該類進行處理。

三.統計邏輯

 由上可知,cluster模式下,token的獲取是由DefaultTokenService來負責的,分為兩種:普通流控和熱點參數流控。二者的實現基本一致,這裡只對普通流控做講解,即DefaultTokenService中的requestToken方法,如下為處理流程。

file

 當請求requestToken方法時,請求參數包括:

 ruleId:規則id

 acquireCount:需要獲取的token數

 prioritized:是否支援優先

  1. DefaultTokenService會先根據ruleId,使用ClusterFlowRuleManager獲得對應的FlowRule規則對象。ClusterFlowRuleManager會在更新規則或者載入規則時根據ruleId快取在Map中,且分配唯一一個ClusterMetric。

  2. 獲得對應的FlowRule對象後,會調用ClusterFlowRuleChecker,判斷是否能夠獲取所需要的token

  3. ClusterFlowRuleChecker會先根據規則Id獲得該規則所對應的namespace,然後判斷該namespace在全局狀態下是否超過流控,該步驟主要由GlobalRequestLimiter提供,該類存儲著各個namespace對應的RequestLimiter對象。RequestLimiter繼承自LeapArray,只提供了QPS一個維度的滑動窗口實現,默認實現為一秒內10個格子,如下圖。全局流控主要使用RequestLimiter的tryPass方法,計算當前qps是否大於規則設定的全局qps。

  4. 全局流控通過後,會根據ClusterMetricStatistics獲取ruleId對應的ClusterMetric,以獲取ruleId對應的統計維度。首先會判斷當前時間是否有可用的token,這裡會根據規則設定的thresholdType,區分設定的閾值模式,如果是全局模式,直接根據設定的值進行限流,如果是單機均攤模式,會將該值乘上已有的額客戶端數達到設定的閾值。如果有則更新統計資訊並返回成功,如果沒有且不支援優先,則直接返回獲取失敗。如果支援優先,則嘗試從下一個格子借用token(註:本地模式的借用會從後面的格子借用,只要不超過最大的等待時間),如果借用成功則更新統計資訊並返回成功,否則返回失敗。ClusterMetric的結構如下,繼承自ClusterMetriceLeapArray,該滑動窗口提供了cluster模式下多種模式的統計數據,還支援請求優先。

file

四.服務端啟動模式

 Sentinel服務端啟動模式可以分為Alone獨立模式和Embedded嵌入模式。

 獨立模式(Alone),即作為獨立的 token server 進程啟動,獨立部署,隔離性好,但是需要額外的部署操作。獨立模式適合作為 Global Rate Limiter 給集群提供流控服務。

file

  1. 在獨立模式下,我們可以直接創建對應的 ClusterTokenServer 實例並在 main 函數中通過 start 方法啟動 Token Server。

  2. 嵌入模式(Embedded),即作為內置的 token server 與服務在同一進程中啟動。在此模式下,集群中各個實例都是對等的,token server 和 client 可以隨時進行轉變,因此無需單獨部署,靈活性比較好。但是隔離性不佳,需要限制 token server 的總 QPS,防止影響應用本身。嵌入模式適合某個應用集群內部的流控。

file

 系統提供了 HTTP API 用於在 embedded 模式下轉換集群流控身份:

http://<ip>:<port>/setClusterMode?mode=<xxx>

 其中 mode 為 0 代表 client,1 代表 server,-1 代表關閉。

 該請求會由ModifyClusterModeCommandHandler處理並最終調用ClusterStateManager.applyState方法來設置當前節點的狀態。需要說明的是,嵌入模式可以不用顯示啟動服務端,而是由上面的applyState模式來設置,該方法會在內部啟動服務。當然也可以不顯示啟動客戶端,同樣通過上面的方法,可以將當前節點設置為客戶端模式。在將當前節點設置為客戶端時,會先獲取當前嵌入模式下的服務端對象,如果不為空,則停止該對象,並啟動服務端;反之在設置服務端時,會先獲取客戶端對象,如果不為空,則先停掉,再啟動嵌入模式下服務端對象。應用啟動接入dashboard後,可以通過管理台來控制各節點的角色,或者通過從配置中心載入規則來更改規則。

五.Handler

 sentinel-transport-common中定義了一套handler介面,用於對外提供HTTP介面同系統交互,從而能夠獲取系統數據或者對應用節點下發命令。

 common模組提供了如下幾個基本介面:

  1. CommandCenter:命令中心,作為服務啟動,定義了start和stop方法,主要提供handler的初始化和註冊服務。

  2. HeartbeatSender:心跳發送介面,用於給控制台dashboard定時發送心跳

  3. CommandHandler:請求處理介面,請求對象為CommandRequest,響應對象為CommandResponse

  4. CommandMapping:註解,用於為Handler添加元數據,包括處理器名(URL路徑名)和描述

file

 針對上面的介面,common模組提供了相對應的Provider類,用於以SPI的方式載入默認/自定義的實現,如上圖,包括:

  1. CommandCenterProvider:根據SPI,載入設定的實現,如果有多個實現,則根據Order註解,選擇優先順序最高的一個

  2. HeartbeatSenderProvider:根據SPI,載入設定的實現,如果有多個實現,則根據Order註解,選擇優先順序最高的一個

  3. CommandHandlerProvider:會載入所有的Handler實現類,不同模組提供的Handler實現只要以SPI的方式,在META-INF中提供對應的全限定名就會被該類掃描並使用。實現類需要增加CommandMapping註解以指定URL。

 如下為common模組提供的Handler實現

file

file

 上圖中common的SPI介面中還有一個InitFunc實現,包括CommandCenterInitFunc和HeartbeatSenderInitFunc兩個實現類,這兩個類實現了InitFunc介面,會在InitExecutor被調用時初始化所有的InitFunc實現。對應的作用為:

 CommandCenterInitFunc:使用CommandCenterProvider獲取對應的CommandCenter實現,依次執行beforeStart和start方法,以啟動服務。即只要載入了sentinel-transport-common模組並通過SPI提供CommandCenter的實現,便會在InitFunc被調用時啟動服務。

 HeartbeatSenderInitFun:HeartbeatSenderProvider獲取對應的HeartbeatSender實現,啟動定時器,每隔5秒執行一次sendHeartbeat方法。即只要載入了sentinel-transport-common模組並通過SPI提供HeartbeatSender的實現,便會在InitFunc被調用時啟動心跳定時器。

 上面提到,只要提供了CommandCenter和HeartbeatSender的實現,並通過SPI註冊對應的實現,並會自動啟動對應的服務,而位於sentinel-transport-simple-http和sentinel-transport-netty的模組為這兩個介面提供了默認實現。

 sentinel-transport-simple-http提供的實現為SimpleHttpCommandCenter和SimpleHttpHeartbeatSender。

 SimpleHttpCommandCenter:基於socket,以阻塞模式提供了簡單的http伺服器,會在啟動前通過CommandHandlerProvider快取所有的Handler對象,當請求進來時新開執行緒處理,並在執行緒中調用對應的Handler進行處理並返回

 SimpleHttpHeartbeatSender:使用內建的SimpleHttpRequest向dashboard發送Http心跳請求

 sentinel-transport-netty提供的實現為NettyHttpCommandCenter和HttpHeartbeatSender。

 NettyHttpCommandCenter:基於netty,以服務端模式啟動,會在啟動前通過CommandHandlerProvider快取所有的Handler對象,內建的HttpServerHandler對象會在請求進來時獲取解碼後的對象,並根據請求類型調用對應的Handler進行處理並返回

 HttpHeartbeatSender:使用httpclient客戶端想dashboard發送Http心跳請求

 綜上,sentinel-cluster-server-default模組提供了如下的Handelr實現,用於給dashboard提供集群資訊並接受從dashboard發送過來的命令。

file

 其中Fetch開頭的為讀取消息,Modify開頭的為修改系統消息。

六.集群管理介面

 Sentinel預留了諸多管理介面,用於動態載入規則或者配置,然後更新本地的狀態,這裡對涉及到cluster模式下的幾個管理介面進行說明。在這之前,先介紹下demo中以Nacos為配置中心的接入方式。

 接入Nacos涉及到另外兩個模組,sentinel-datasource-extension和sentinel-datasource-nacos。Extension模組定義了ReadableDataSource介面,用於從數據源讀取數據,返回配置數據SentinelProperty。Extension模組提供了一個抽象類實現AbstractDataSource,實現了loadConfig方法。該類引入了Converter介面和DynamicSentinelProperty類,Converter介面用於將數據源中讀取的數據結構轉換為SentienlProperty存儲的數據格式;DynameicSentinelProperty類為SentinelPorperty的默認實現,該類能夠添加多個PropertyListener監聽器,在添加時觸發監聽器的configLoad方法進行監聽器的初次動作,並在數據發生變更時,逐個通知監聽器,調用監聽器的configLoad方法,提醒監聽器進行更新。AbstractDataSource實現了loadConfig方法,該方法會調用readSource方法,從數據源讀取原始數據,並調用Converter進行數據轉換。

file

 Nacos模組提供了NacosDataSource實現,繼承自AbstractDataSource,以接入Nacos配置中心。NacosDataSource在初始化時會在Nacos上申請一個配置集,並添加監聽器,然後執行一遍loadConfig,從配置中心載入一遍配置並,更新property中的值並通知配置集上的監聽器。Nacos上的監聽器會在配置發生變化時,調用Convert記性處理,並更新配置集,同時通知配置集上的監聽器。

 由上可知,可以通過使用DynamicSentinelProperty動態配置集上的監聽器,配合數據眼監聽配置變化,從而讓系統做出相應的動作。事實上,sentinel內置的大部分管理介面都是這樣處理的,如下為集群相關的主要管理介面,均以Manager結尾。這些管理介面的結構都同FlowRuleManager一樣,內部維護這一個或者多個配置源,並在配置源上設置了監聽器,當配置源有數據變化時,會調用配置源的updateValue方法,更新配置源數據並且通知監聽器。

  1. FlowRuleManager

     這個在講解sentinel-core模組時有介紹過,主要是存儲本地限流規則集SentinelProperty<List>。該規則集上有FlowPropertyListener監聽器,會在規則發生變更時重新構建,載入規則。

  2. ParamFlowRuleManager

     同FlowRuleManager,主要用於熱點參數限流規則管理。

  3. ClusterClientConfigManager

     集群客戶端配置管理,主要管理:

    1) 集群客戶端配置,用於設定客戶端超時時間,配置集為SentinelProperty和監聽器ClientConfigPropertyListener。會在規則發生變更時,更新客戶端的請求超時時間

    2) 集群服務端資訊配置,用於設定服務端的ip和埠資訊,配置集為SentinelProperty和監聽器ClientAssignPropertyListener。會在規則發送變更時,更新本地配置,並通知ServerChangeObserver觀察者服務端節點發送了變化,由之前的內容可以看到,DefaultClusterTokenClient為該介面的觀察者,會在服務端資訊發送變更時先斷開同之前的鏈接,再同心的服務端節點建立新的鏈接。

  4. ClusterServerConfigManager

     集群服務端配置管理,主要管理:

    1) 集群服務端傳輸配置,用於設定服務端埠和idle時間,配置集為SentinelProperty和監聽器ServerGlobalTransportPropertyListener。會在規則發生變更時,更新本地配置,並通知ServerTransportConfigObserver觀察者配置發生了變化。由之前的內容可以看到,SentinelDefaultTokenServer為該介面的觀察者,會在服務端資訊發送變更時,停止自身應用,再重新啟動。

    2) 集群服務端全局流控配置,用於設定全局流控配置項,包括滑動窗口實現大小,窗口格子數,允許通過的最大qps等。配置集為SentinelProperty和監聽器ServerGlobalFlowPropertyListener,會在規則更新時重新設置這些配置內容。

    3) 集群服務端namespace集合配置,用於設定集群中的namespace集合,配置集為SentinelProperty<Set>和監聽器ServerNamespaceSetPropertyListener,會在配置發生變更時移除老namesapce的配置,並重新載入新namesapce的配置,包括對應的全局限流器GlobalRequestLimiter,集群限流規則,集群熱點限流規則。

  5. ClusterFlowRuleManager

     集群限流規則配置管理,主要管理:

    1) 集群規則配置,用於設定集群規則,配置集為SentinelProperty<List>和監聽器FlowRulePropertyListener,會在配置發生變更時,移除對應namespace下的快取的配置,並重新構建對應的規則。對於一個新的flowId,會為其分配一個對應的ClusterMetricStatistics統計節點。

  6. ClusterParamFlowRuleManager

     集群熱點限流規則配置管理,同ClusterFlowRuleManager

  7. ClusterStateManager

     集群全局狀態管理,主要管理:

    1) 本機角色配置,配置集為SentinelProperty和監聽器ClusterStatePropertyListener,會在規則發生變更時,調整本機的角色。角色包括:服務端,客戶端和非集群模式。若規則為非集群模式,則會停止相關的客戶端或者服務端;若設置為服務端模式,則會使用嵌入模式啟動服務,若之前為客戶端則會關閉客戶端連接;若設置為客戶端模式,則會啟動客戶端連接,若之前為服務端則會停止服務。

 上述幾個管理介面都可以接入配置中心如Nacos,以通過配置中心和管理台來改變各配置項。

file

個人公眾號:啊駝