深度解析微服務(wù)高并發(fā)資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì):資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)全解析
資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)全解析
本節(jié)將結(jié)合前面所學(xué)的內(nèi)容,以及基于滑動(dòng)窗口實(shí)現(xiàn)資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì),分析Sentinel是如何實(shí)現(xiàn)資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)的。
節(jié)點(diǎn)選擇器插槽
節(jié)點(diǎn)選擇器插槽(NodeSelectorSlot)負(fù)責(zé)為資源的首次訪問創(chuàng)建DefaultNode實(shí)例,以及修改Context實(shí)例的curNode字段指向當(dāng)前資源的DefaultNode實(shí)例,將DefaultNode實(shí)例綁定到調(diào)用樹上。因?yàn)楹罄m(xù)的ProcessorSlot在邏輯上都需要依賴這個(gè)ProcessorSlot,所以它被放在ProcessorSlot鏈表的第一個(gè)位置。NodeSelectorSlot的源碼如下。

如源碼所示,map字段是一個(gè)非靜態(tài)字段,意味著每個(gè)NodeSelectorSlot實(shí)例都有一個(gè)Map實(shí)例。而Sentinel只會(huì)為一個(gè)資源創(chuàng)建一個(gè)ProcessorSlotChain,一個(gè)ProcessorSlotChain又只會(huì)創(chuàng)建一個(gè)NodeSelectorSlot,并且map字段緩存DefaultNode使用的key并非資源ID,而是調(diào)用鏈入口名稱。所以,map字段的作用是緩存同一資源、不同調(diào)用鏈入口創(chuàng)建的DefaultNode實(shí)例。
Sentinel會(huì)為同一資源創(chuàng)建多少個(gè)DefaultNode實(shí)例取決于有多少個(gè)入口節(jié)點(diǎn)不同的調(diào)用鏈包含這個(gè)資源,這就是為什么說一個(gè)資源可能有多個(gè)DefaultNode實(shí)例的原因。
為什么這么設(shè)計(jì)呢?舉個(gè)例子,對(duì)于同一支付接口,我們既可以使用Spring MVC暴露給前端訪問,也可以使用Dubbo暴露給其他內(nèi)部服務(wù)調(diào)用。由于入口節(jié)點(diǎn)不同,支付接口會(huì)被兩條調(diào)用鏈包含。針對(duì)這種情況,我們可以通過設(shè)置來限制從Spring MVC進(jìn)來的流量,也就是對(duì)前端請(qǐng)求限流。
NodeSelectorSlot類的entry方法最難理解的就是,將當(dāng)前資源的DefaultNode實(shí)例綁定到調(diào)用樹的如下代碼:

這行代碼可以分為兩種情況來分析,接下來以Sentinel提供的demo為例進(jìn)行分析。
1. 一般情況
Sentinel的sentinel-demo模塊提供了多種使用場(chǎng)景的demo,我們以sentinel-demo-springwebmvc這個(gè)demo為例進(jìn)行講解。該demo下有一個(gè)hello接口,其代碼如下。

提示:這里不需要添加任何規(guī)則,只是為了調(diào)試Sentinel的源碼。
在啟動(dòng)demo后,使用瀏覽器訪問hello接口,在NodeSelectorSlot類的entry方法的綁定調(diào)用樹這一行代碼下斷點(diǎn),觀察此時(shí)Context實(shí)例的字段信息。在正常情況下,我們可以看到如圖4.4所示的結(jié)果。

圖4.4 綁定調(diào)用樹1
從圖4.4中可以看出,此時(shí)調(diào)用鏈入口節(jié)點(diǎn)(entranceNode)的子節(jié)點(diǎn)(childList)為空,并且當(dāng)前CtEntry實(shí)例(curEntry)的父(parent)、子(child)節(jié)點(diǎn)都是Null。當(dāng)綁定調(diào)用樹這一行代碼執(zhí)行完成后,Context實(shí)例的字段信息如圖4.5所示。

圖4.5 綁定調(diào)用樹2
從圖4.5中可以看出,NodeSelectorSlot為當(dāng)前資源創(chuàng)建的DefaultNode實(shí)例被添加到了調(diào)用鏈入口節(jié)點(diǎn)(entranceNode)的子節(jié)點(diǎn)(childList)中。
此時(shí),ROOT節(jié)點(diǎn)、調(diào)用鏈入口節(jié)點(diǎn)及當(dāng)前資源的DefaultNode節(jié)點(diǎn)構(gòu)造成的調(diào)用樹如下。

如果現(xiàn)在訪問demo的其他接口,如訪問err接口,則將會(huì)生成如下所示的調(diào)用樹。

提示:名稱為
sentinel_spring_web_context的調(diào)用鏈入口節(jié)點(diǎn)將會(huì)存儲(chǔ)Web項(xiàng)目中所有資源的DefaultNode節(jié)點(diǎn)。
2. 存在多次調(diào)用SphU#entry方法的情況
如果在一個(gè)服務(wù)中既添加了Sentinel的WebMvc適配模塊的依賴,也添加了Sentinel的OpenFeign適配模塊的依賴,并且使用了OpenFeign調(diào)用內(nèi)部其他服務(wù)的接口,就會(huì)存在一次調(diào)用鏈路上出現(xiàn)多次調(diào)用SphU#entry方法的情況。
WebMvc適配器在接收客戶端請(qǐng)求時(shí)會(huì)調(diào)用一次SphU#entry方法,在處理客戶端請(qǐng)求時(shí)可能需要使用OpenFeign調(diào)用內(nèi)部其他服務(wù)的接口,那么在發(fā)起接口調(diào)用時(shí),Sentinel的OpenFeign適配器也會(huì)調(diào)用一次SphU#entry方法。
現(xiàn)在將demo的hello接口修改一下,將hello接口調(diào)用的doBusiness方法也作為資源,并使用Sentinel保護(hù)起來,改造后的hello接口代碼如下。

我們可以將doBusiness方法看作遠(yuǎn)程調(diào)用。例如,使用POST方式調(diào)用第三方的接口,接口名稱為hello2,那么我們可以使用POST:/hello2作為資源名稱,并將流量類型設(shè)置為OUT類型,將上下文名稱設(shè)置為my_context。
在啟動(dòng)demo后,使用瀏覽器訪問hello接口。當(dāng)代碼執(zhí)行到apiHello方法時(shí),在NodeSelectorSlot#entry方法的綁定調(diào)用樹這一行代碼下斷點(diǎn),當(dāng)綁定調(diào)用樹這一行代碼執(zhí)行完成后,Context實(shí)例的字段信息如圖4.6所示。
從圖4.6中可以看出,Sentinel并沒有創(chuàng)建名稱為my_context的Context實(shí)例,因?yàn)楫?dāng)前調(diào)用鏈上已經(jīng)存在Context實(shí)例,Sentinel只是在調(diào)用鏈入口處創(chuàng)建了Context實(shí)例。

圖4.6 綁定調(diào)用樹3
在執(zhí)行NodeSelectorSlot#entry方法之前,由于還沒有為名稱為POST:/hello2的資源創(chuàng)建ProcessorSlotChain,因此SphU#entry方法會(huì)為該資源創(chuàng)建一個(gè)ProcessorSlotChain,并為該P(yáng)rocessorSlotChain創(chuàng)建一個(gè)NodeSelectorSlot。當(dāng)執(zhí)行到NodeSelectorSlot#entry方法時(shí),該方法就會(huì)為該資源創(chuàng)建一個(gè)DefaultNode實(shí)例,而將該資源的DefaultNode實(shí)例綁定到節(jié)點(diǎn)樹后,該資源的DefaultNode實(shí)例就會(huì)成為GET:/hello資源的DefaultNode實(shí)例的子節(jié)點(diǎn),此時(shí)調(diào)用樹如下。

此時(shí),當(dāng)前調(diào)用鏈路上已經(jīng)存在兩個(gè)CtEntry實(shí)例,這兩個(gè)CtEntry實(shí)例構(gòu)造了一個(gè)雙向鏈表,如圖4.7所示。

圖4.7 CtEntry構(gòu)造的雙向鏈表
雖然存在兩個(gè)CtEntry實(shí)例,但此時(shí)Context實(shí)例的curEntry字段指向的是第二個(gè)CtEntry實(shí)例,第二個(gè)CtEntry實(shí)例是在apiHello方法調(diào)用SphU#entry方法時(shí)創(chuàng)建的。在執(zhí)行完doBusiness方法后,需要調(diào)用當(dāng)前CtEntry#exit方法,由該CtEntry將Context實(shí)例的curEntry字段還原為指向該CtEntry的父CtEntry。
接下來分析NodeSelectorSlot#entry方法中的另一行代碼,代碼如下。

這行代碼的作用是將當(dāng)前創(chuàng)建的DefaultNode實(shí)例賦值給當(dāng)前CtEntry實(shí)例的curNode字段。結(jié)合圖4.7來理解,就是將資源GET:/hello的DefaultNode實(shí)例賦值給第一個(gè)CtEntry實(shí)例的curNode字段,將資源POST:/hello2的DefaultNode實(shí)例賦值給第二個(gè)CtEntry實(shí)例的curNode字段。
ClusterNode構(gòu)造器插槽
在一個(gè)資源的ProcessorSlotChain中,NodeSelectorSlot負(fù)責(zé)為資源創(chuàng)建DefaultNode實(shí)例,而這個(gè)DefaultNode實(shí)例僅限同一入口的調(diào)用鏈?zhǔn)褂?。所以,一個(gè)資源可能會(huì)存在多個(gè)DefaultNode實(shí)例,那么想要獲取一個(gè)資源的總QPS,就必須遍歷這些DefaultNode實(shí)例。出于性能考慮,Sentinel會(huì)為每個(gè)資源創(chuàng)建一個(gè)全局唯一的ClusterNode實(shí)例,用于統(tǒng)計(jì)資源的全局指標(biāo)數(shù)據(jù)。
與NodeSelectorSlot的職責(zé)相似,ClusterBuilderSlot的職責(zé)是為資源創(chuàng)建全局唯一的ClusterNode實(shí)例,且僅在資源第一次被訪問時(shí)創(chuàng)建。
ClusterBuilderSlot在創(chuàng)建ClusterNode實(shí)例時(shí),需要將ClusterNode實(shí)例賦值給DefaultNode實(shí)例的clusterNode字段,由DefaultNode實(shí)例持有ClusterNode實(shí)例,并由DefaultNode負(fù)責(zé)代理ClusterNode完成資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)。
必須先有DefaultNode實(shí)例,才能將ClusterNode實(shí)例委托給DefaultNode實(shí)例,這就是ClusterBuilderSlot在ProcessorSlotChain中必須被放在NodeSelectorSlot之后的原因。
ClusterBuilderSlot聲明的字段如下。

因?yàn)橐粋€(gè)資源只會(huì)有一個(gè)ProcessorSlotChain,這就意味著ClusterBuilderSlot只會(huì)被創(chuàng)建一個(gè),那么讓ClusterBuilderSlot持有資源的ClusterNode實(shí)例,就可以省去每次都從clusterNodeMap靜態(tài)字段中獲取資源的ClusterNode實(shí)例的步驟,這當(dāng)然也是出于性能方面的考慮。
ClusterBuilderSlot類的entry方法的源碼如下。

① 如果clusterNode字段為空,說明當(dāng)前資源首次被訪問,那么需要為資源創(chuàng)建一個(gè)全局唯一的ClusterNode實(shí)例。
② 將ClusterNode實(shí)例委托給資源的DefaultNode實(shí)例來統(tǒng)計(jì)資源指標(biāo)數(shù)據(jù),node參數(shù)為NodeSelectorSlot傳遞過來的DefaultNode實(shí)例。
③ 如果調(diào)用來源不為空,那么為當(dāng)前調(diào)用來源創(chuàng)建一個(gè)StatisticNode實(shí)例。
ClusterBuilderSlot將ClusterNode實(shí)例賦值給DefaultNode實(shí)例的clusterNode字段,后續(xù)的ProcessorSlot就能從entry方法的node參數(shù)中獲取ClusterNode實(shí)例。DefaultNode與ClusterNode的關(guān)系如圖4.8所示。

圖4.8 DefaultNode與ClusterNode的關(guān)系
每個(gè)ClusterNode實(shí)例都有一個(gè)Map類型的字段,用來緩存調(diào)用來源(origin)與StatisticNode實(shí)例的映射,代碼如下。

?originCountMap:如果上游服務(wù)調(diào)用當(dāng)前服務(wù)的接口將origin字段傳遞過來,那么ClusterBuilderSlot就會(huì)為ClusterNode實(shí)例創(chuàng)建一個(gè)StatisticNode實(shí)例,用來統(tǒng)計(jì)當(dāng)前資源被該遠(yuǎn)程服務(wù)調(diào)用的指標(biāo)數(shù)據(jù)。
提示:例如,上游服務(wù)在發(fā)送HTTP請(qǐng)求時(shí),在請(qǐng)求頭添加S-user參數(shù),或者上游服務(wù)在發(fā)送Dubbo RPC調(diào)用時(shí),在請(qǐng)求參數(shù)列表添加application參數(shù),就能獲取來源應(yīng)用名稱。
當(dāng)我們想要查看哪個(gè)來源應(yīng)用訪問這個(gè)接口最頻繁時(shí),可以從ClusterNode實(shí)例的originCountMap字段,根據(jù)來源應(yīng)用名稱獲取StatisticNode實(shí)例,從而獲取QPS,并據(jù)此實(shí)現(xiàn)按調(diào)用來源限流。
ClusterNode#getOrCreateOriginNode方法的源碼如下。

為了便于使用,ClusterBuilderSlot會(huì)將調(diào)用來源的StatisticNode實(shí)例賦值給CtEntry實(shí)例的originNode字段,后續(xù)的ProcessorSlot可先調(diào)用Context實(shí)例的getCurEntry方法獲取CtEntry實(shí)例,再調(diào)用CtEntry實(shí)例的getOriginNode方法即可獲取該StatisticNode實(shí)例。
這里我們可以得出一個(gè)結(jié)論,如果自定義的ProcessorSlot需要用到調(diào)用來源的StatisticNode,那么在構(gòu)建ProcessorSlotChain時(shí),必須將這個(gè)自定義的ProcessorSlot放在ClusterBuilderSlot之后。
資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)插槽
StatisticSlot是實(shí)現(xiàn)資源各項(xiàng)指標(biāo)數(shù)據(jù)統(tǒng)計(jì)的處理器插槽,它與NodeSelectorSlot、ClusterBuilderSlot共同組成了資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)流水線。
NodeSelectorSlot負(fù)責(zé)為資源創(chuàng)建DefaultNode實(shí)例,并將DefaultNode實(shí)例向下傳遞給ClusterBuilderSlot;ClusterBuilderSlot則負(fù)責(zé)加工資源的DefaultNode實(shí)例,添加ClusterNode實(shí)例,然后將DefaultNode實(shí)例向下傳遞給StatisticSlot,如圖4.9所示。

圖4.9 資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)流水線
StatisticSlot在統(tǒng)計(jì)指標(biāo)數(shù)據(jù)之前會(huì)先調(diào)用后續(xù)的ProcessorSlot,再根據(jù)后續(xù)ProcessorSlot判斷是否需要拒絕當(dāng)前請(qǐng)求的結(jié)果并決定記錄哪些指標(biāo)數(shù)據(jù)。StatisticSlot的源碼框架如下。

?entry:先通過fireEntry方法調(diào)用后續(xù)的ProcessorSlot#entry方法,再根據(jù)后續(xù)的ProcessorSlot是否拋出BlockException來決定統(tǒng)計(jì)哪些指標(biāo)數(shù)據(jù),并將資源并行占用的線程數(shù)加1。
? exit:若無任何異常,則統(tǒng)計(jì)請(qǐng)求成功、請(qǐng)求執(zhí)行耗時(shí)指標(biāo),并將資源并行占用的線程數(shù)減1。
從StatisticSlot#entry方法的源碼中可以看出,為什么Sentinel設(shè)計(jì)的責(zé)任鏈需要由前一個(gè)ProcessorSlot在entry方法或exit方法中調(diào)用fireEntry方法或fireExit方法以調(diào)用下一個(gè)ProcessorSlot的entry方法或exit方法,而不是使用for循環(huán)遍歷調(diào)用ProcessorSlot。因?yàn)槊總€(gè)ProcessorSlot都有權(quán)決定先等后續(xù)的ProcessorSlot執(zhí)行完成再做自己的事情,還是先完成自己的事情再讓后續(xù)的ProcessorSlot執(zhí)行,這與流水線有所區(qū)別。
1. entry方法
第一種情況:當(dāng)后續(xù)的ProcessorSlot未拋出任何異常時(shí),表示不需要拒絕當(dāng)前請(qǐng)求,當(dāng)前請(qǐng)求會(huì)被放行。
如果當(dāng)前請(qǐng)求被放行,則需要將當(dāng)前資源并行占用的線程數(shù)加1,將當(dāng)前時(shí)間窗口被放行的請(qǐng)求總數(shù)加1,代碼如下。

如果調(diào)用來源不為空,也將調(diào)用來源對(duì)應(yīng)的StatisticNode的當(dāng)前并行占用線程數(shù)加1,將當(dāng)前時(shí)間窗口被放行的請(qǐng)求數(shù)加1,代碼如下。
如果流量類型為IN,則讓統(tǒng)計(jì)整個(gè)應(yīng)用所有流入類型流量的ENTRY_NODE自增并行占用的線程數(shù)、當(dāng)前時(shí)間窗口被放行的請(qǐng)求數(shù)加1,代碼如下。
回調(diào)所有的ProcessorSlotEntryCallback的onPass方法,代碼如下。
調(diào)用
StatisticSlotCallbackRegistry#addEntryCallback靜態(tài)方法注冊(cè)
ProcessorSlotEntryCallback。ProcessorSlotEntryCallback接口的定義如下。
? onPass:該方法在請(qǐng)求被放行時(shí)被回調(diào)執(zhí)行。
? onBlocked:該方法在請(qǐng)求被拒絕時(shí)被回調(diào)執(zhí)行。
第二種情況:捕獲到PriorityWaitException。
這是特殊情況,在需要對(duì)請(qǐng)求限流時(shí),只有使用默認(rèn)流量效果控制器才可能會(huì)拋出PriorityWaitException,這部分內(nèi)容將在講解FlowSlot的實(shí)現(xiàn)源碼時(shí)再做分析。
當(dāng)捕獲到PriorityWaitException時(shí),說明當(dāng)前請(qǐng)求已經(jīng)被休眠了一段時(shí)間了,但還是允許請(qǐng)求通過的,只是不需要讓DefaultNode實(shí)例統(tǒng)計(jì)這個(gè)請(qǐng)求了,只自增當(dāng)前資源并行占用的線程數(shù),同時(shí),DefaultNode實(shí)例也會(huì)讓ClusterNode實(shí)例自增并行占用的線程數(shù),最后會(huì)回調(diào)所有
ProcessorSlotEntryCallback#onPass方法。這部分的源碼如下。
第三種情況:捕獲到BlockException。
BlockException只在需要拒絕請(qǐng)求時(shí)被拋出。捕獲到BlockException時(shí)執(zhí)行的代碼如下。
①當(dāng)捕獲到BlockException時(shí),將異常保存到調(diào)用鏈上下文的當(dāng)前CtEntry實(shí)例中,StatisticSlot的exit方法會(huì)識(shí)別是統(tǒng)計(jì)請(qǐng)求異常指標(biāo)還是統(tǒng)計(jì)請(qǐng)求被拒絕指標(biāo)。
②調(diào)用DefaultNode#increaseBlockQps方法自增請(qǐng)求被拒絕總數(shù),將當(dāng)前時(shí)間窗口的block qps這項(xiàng)指標(biāo)數(shù)據(jù)的值加1。
③ 如果調(diào)用來源不為空,則讓調(diào)用來源對(duì)應(yīng)的StatisticNode實(shí)例統(tǒng)計(jì)的請(qǐng)求被拒絕總數(shù)加1。
④ 如果流量類型為IN,則讓ENTRY_NODE統(tǒng)計(jì)的請(qǐng)求被拒絕總數(shù)加1。
⑤ 回調(diào)所有的
ProcessorSlotEntryCallback#onBlocked方法。
StatisticSlot捕獲BlockException只是為了統(tǒng)計(jì)請(qǐng)求被拒絕的總數(shù),而BlockException還是會(huì)被向上拋出。拋出異常的目的是攔住請(qǐng)求,執(zhí)行服務(wù)降級(jí)處理。
第四種情況:捕獲到其他異常。
其他異常并非指業(yè)務(wù)異常,因?yàn)榇藭r(shí)業(yè)務(wù)代碼還未被執(zhí)行,而業(yè)務(wù)代碼拋出的異常,會(huì)通過調(diào)用Tracer#trace方法統(tǒng)計(jì)請(qǐng)求異??倲?shù)。
當(dāng)捕獲到非BlockException時(shí),除PriorityWaitException外,其他類型的異常都進(jìn)行同樣的處理:讓資源的DefaultNode實(shí)例自增當(dāng)前時(shí)間窗口的請(qǐng)求異??倲?shù);讓調(diào)用來源的StatisticNode實(shí)例、統(tǒng)計(jì)所有IN類型流量的ENTRY_NODE自增當(dāng)前時(shí)間窗口的請(qǐng)求異??倲?shù)。這部分的源碼如下。
① 將異常保存到調(diào)用鏈上下文的當(dāng)前Entry實(shí)例中。
②調(diào)用DefaultNode#increaseExceptionQps方法統(tǒng)計(jì)異常指標(biāo),將當(dāng)前時(shí)間窗口的exception qps這項(xiàng)指標(biāo)數(shù)據(jù)的值加1。
③ 如果調(diào)用來源不為空,則讓調(diào)用來源的StatisticNode實(shí)例統(tǒng)計(jì)異常指標(biāo)。
④ 如果流量類型為IN,則讓ENTRY_NODE統(tǒng)計(jì)異常指標(biāo)。⑤ 拋出異常。
2. exit方法
當(dāng)exit方法被調(diào)用時(shí),要么請(qǐng)求被拒絕,要么請(qǐng)求被放行且已經(jīng)被執(zhí)行完成,所以exit方法需要知道當(dāng)前請(qǐng)求是否被正常執(zhí)行完成,這正是StatisticSlot在捕獲異常時(shí)將異常保存到當(dāng)前CtEntry實(shí)例的原因。
exit方法通過Context實(shí)例可以獲取當(dāng)前CtEntry實(shí)例,從當(dāng)前CtEntry實(shí)例中可以獲取entry方法中保存的異常。exit方法的源碼如下(有刪減)。

① exit方法通過Context實(shí)例可以獲取當(dāng)前資源的DefaultNode實(shí)例,如果entry方法中未出現(xiàn)異常,則說明請(qǐng)求是正常完成的。
② 當(dāng)計(jì)算耗時(shí)時(shí),可以將當(dāng)前時(shí)間減去調(diào)用鏈上當(dāng)前CtEntry實(shí)例的創(chuàng)建時(shí)間的值作為請(qǐng)求的執(zhí)行耗時(shí)。
③ 在請(qǐng)求被正常完成的情況下,需要統(tǒng)計(jì)總耗時(shí)指標(biāo),增加當(dāng)前請(qǐng)求的執(zhí)行耗時(shí),統(tǒng)計(jì)成功請(qǐng)求總數(shù),將成功請(qǐng)求總數(shù)加1。
④ 如果調(diào)用來源不為空,則讓調(diào)用來源的StatisticNode實(shí)例統(tǒng)計(jì)總耗時(shí)指標(biāo),增加當(dāng)前請(qǐng)求的執(zhí)行耗時(shí),統(tǒng)計(jì)成功請(qǐng)求總數(shù),將成功請(qǐng)求總數(shù)加1。
⑤ 恢復(fù)當(dāng)前資源占用的線程數(shù)。
⑥ 如果調(diào)用來源不為空,則恢復(fù)當(dāng)前調(diào)用來源占用的線程數(shù)。
⑦如果流量類型為IN,則讓ENTRY_NODE統(tǒng)計(jì)總耗時(shí)指標(biāo),增加當(dāng)前請(qǐng)求的執(zhí)行耗時(shí),統(tǒng)計(jì)成功請(qǐng)求總數(shù),將成功請(qǐng)求總數(shù)加1,恢復(fù)占用的線程數(shù)。
⑧ 回調(diào)所有ProcessorSlotExitCallback#onExit方法。
資源指標(biāo)數(shù)據(jù)的收集過程
ClusterNode是一個(gè)資源全局的指標(biāo)數(shù)據(jù)統(tǒng)計(jì)節(jié)點(diǎn),但我們并未在StatisticSlot的entry方法與exit方法中看到其被使用。這里實(shí)際上使用了委托模式,ClusterNode被ClusterBuilderSlot委托給DefaultNode統(tǒng)計(jì)指標(biāo)數(shù)據(jù),如下述代碼所示。

當(dāng)請(qǐng)求被成功處理后,StatisticSlot會(huì)調(diào)用DefaultNode實(shí)例的addRtAndSuccess方法增加請(qǐng)求處理成功總數(shù)和總耗時(shí);DefaultNode會(huì)先調(diào)用父類的addRtAndSuccess方法,再調(diào)用ClusterNode實(shí)例的addRtAndSuccess方法。ClusterNode類與DefaultNode類都是StatisticNode類的子類。StatisticNode類的addRtAndSuccess方法的源碼如下。

rollingCounterInSecond是一個(gè)秒級(jí)滑動(dòng)窗口,rollingCounterInMinute是一個(gè)分鐘級(jí)滑動(dòng)窗口,類型都為ArrayMetric。分鐘級(jí)滑動(dòng)窗口共有60個(gè)MetricBucket,每個(gè)MetricBucket都會(huì)被WindowWrap數(shù)組包裝,用于統(tǒng)計(jì)1秒內(nèi)的各項(xiàng)指標(biāo)數(shù)據(jù),如圖4.10所示。

圖4.10 WindowWrap數(shù)組
當(dāng)調(diào)用rollingCounterInMinute的addSuccess方法時(shí),先由滑動(dòng)窗口根據(jù)當(dāng)前時(shí)間戳獲取當(dāng)前時(shí)間窗口的MetricBucket實(shí)例,再調(diào)用MetricBucket實(shí)例的addSuccess方法,將success這項(xiàng)指標(biāo)的值加上方法參數(shù)successCount的值(一般是1)。
Sentinel在MetricEvent枚舉類中定義了Sentinel會(huì)收集的指標(biāo)數(shù)據(jù)。MetricEvent枚舉類的源碼如下。

? PASS指標(biāo):請(qǐng)求被放行的總數(shù)。
? BLOCK指標(biāo):請(qǐng)求被拒絕的總數(shù)。
? EXCEPTION指標(biāo):異常的請(qǐng)求總數(shù)。
? SUCCESS指標(biāo):被成功處理的請(qǐng)求總數(shù)。
? RT指標(biāo):被成功處理的請(qǐng)求的總耗時(shí)。
?OCCUPIED_PASS指標(biāo):預(yù)通過總數(shù)(前一個(gè)時(shí)間窗口使用了當(dāng)前時(shí)間窗口的passQps)。
其他一些指標(biāo)數(shù)據(jù)都可以通過以上指標(biāo)數(shù)據(jù)計(jì)算得出,例如,被成功處理的請(qǐng)求的平均耗時(shí)可以根據(jù)被成功處理的請(qǐng)求的總耗時(shí)除以被成功處理的請(qǐng)求總數(shù)計(jì)算得出。
小結(jié)
本篇主要介紹了Sentinel如何實(shí)現(xiàn)基于滑動(dòng)窗口統(tǒng)計(jì)資源的實(shí)時(shí)指標(biāo)數(shù)據(jù)、Sentinel資源指標(biāo)數(shù)據(jù)統(tǒng)計(jì)流程分析,以及調(diào)用樹的結(jié)構(gòu),同時(shí)分析了NodeSelectorSlot、ClusterBuilderSlot和StatisticSlot這幾個(gè)處理器插槽的用途及它們之間的聯(lián)系。