則與系統(tǒng)規(guī)則實戰(zhàn):白名單、黑名單與整機兜底)
Sentinel系列寫到第五篇前幾篇我們把流控規(guī)則、降級規(guī)則、熱點規(guī)則都過了一遍到這篇剩下的就是授權規(guī)則和系統(tǒng)規(guī)則兩個模塊。標題上雖然寫了“簡要說明”但以我實際排查問題的經(jīng)驗來看這兩個模塊看著簡單用起來翻車的概率比流控規(guī)則還高。很多人配了授權規(guī)則不生效配了系統(tǒng)規(guī)則發(fā)現(xiàn)接口全被攔了問題往往出在沒搞懂這兩類規(guī)則在Sentinel整個攔截鏈里到底處于什么位置以及它們背后依賴了哪些前置條件。這篇就用實戰(zhàn)的方式把這兩塊拆開講清楚適合已經(jīng)跑通Sentinel基礎流程、準備在生產(chǎn)環(huán)境細化管控的同學參考。1. 授權規(guī)則與系統(tǒng)規(guī)則流控之外的“橫向兜底”從Sentinel的規(guī)則體系來看流控、降級、熱點規(guī)則針對的是單個資源解決的是“某個接口能不能扛住這個量”的問題而授權規(guī)則和系統(tǒng)規(guī)則解決的是另一類問題一個請求到底有沒有資格進來以及這個應用整體還能不能撐住。這兩類規(guī)則是橫向的作用在整個調用鏈上不單純掛在某一個資源名下。1.1 授權規(guī)則到底在管什么授權規(guī)則的核心是“我不能讓誰進來”區(qū)分白名單和黑名單兩種模式。舉個例子線上有個下單接口原本只應該由訂單服務內部調用結果被外部某個腳本盯上了每天定時刷你幾千次。流量不大觸發(fā)不了流控規(guī)則但接口邏輯確實在被白嫖業(yè)務上也說不清這些請求到底是哪來的。這時候授權規(guī)則就能派上用場給請求來源打上標記在Sentinel入口處直接拒絕黑名單來源或者反過來只允許白名單來源訪問。它的設計思路是“來源識別 黑白名單判斷”所以有一個關鍵前提請求里必須能被解析出“來源應用”。這個來源可以是一個Header、一個參數(shù)、甚至一段自定義邏輯。如果應用里沒做來源解析授權規(guī)則就是一堆永遠不生效的配置。1.2 系統(tǒng)規(guī)則是給“整機”設的保險絲系統(tǒng)規(guī)則的價值不在于保護某個接口而在于保護整個應用進程。它的閾值維度包括系統(tǒng)Load、CPU使用率、入口QPS、平均RT、線程數(shù)五個維度任何一項超標都會觸發(fā)全局攔截。典型的場景是業(yè)務大促期間某個接口流量暴漲流控規(guī)則還沒來得及在對應的資源上生效整個應用就快被拖垮了。這時系統(tǒng)規(guī)則像保險絲一樣先跳閘保住進程不被打掛。這套機制的思路是“機器級別的兜底”不分接口、不分調用方一視同仁地限制入口流量。但它也有偏科的地方比如你設置了入口QPS為1000結果所有Web接口的請求都會算進這1000如果某個接口本身會調用外部服務導致RT很高也可能把全局平均RT拉高觸發(fā)攔截。所以系統(tǒng)規(guī)則的閾值設置比流控規(guī)則更依賴對整機負載的理解不能拍腦袋填數(shù)字。2. 授權規(guī)則從配置到落地請求來源解析是關鍵授權規(guī)則看起來只有兩個選項白名單、黑名單再加一個來源應用名稱。但實際上最容易出問題的環(huán)節(jié)是“來源應用名稱從哪來”。Sentinel不會自動知道調用方是誰它需要你告訴它一個來源標識。2.1 授權規(guī)則的判定邏輯授權規(guī)則在Sentinel攔截鏈中位于統(tǒng)計完成之后、流控規(guī)則之前核心類是AuthoritySlot。當一個請求進入時Sentinel會調用RequestOriginParser解析出origin然后拿著這個origin和當前資源的授權規(guī)則做匹配。白名單模式下limitApp里列出的來源允許通過未列出的來源直接拋AuthorityException黑名單模式正好反過來列出的來源被拒絕未列出的放行。這里有個容易被忽略的細節(jié)如果請求解析出來的origin為空或者為null在白名單模式下請求會直接拒絕。原因很簡單白名單的語義是“只有列表里的才放行”一個連來源都標識不出來的請求自然不在信任范圍內。所以實現(xiàn)RequestOriginParser時至少要給一個兜底值比如default避免誤傷正常請求。2.2 來源解析RequestOriginParser的實現(xiàn)細節(jié)在Spring Boot的Web MVC場景下spring-cloud-starter-alibaba-sentinel自動裝配了SentinelWebInterceptor它會從Spring容器中查找RequestOriginParser的實現(xiàn)類來解析來源。實現(xiàn)這個接口很簡單Component public class HeaderRequestOriginParser implements RequestOriginParser { Override public String parseOrigin(HttpServletRequest request) { // 優(yōu)先取自定義Header很多內部系統(tǒng)約定用X-Origin標識調用來源 String origin request.getHeader(X-Origin); if (origin ! null origin.trim().length() 0) { return origin; } // 兜底返回一個固定值避免白名單模式下誤傷沒有來源標識的請求 return default; } }這個類必須是Spring管理的Bean否則SentinelWebInterceptor拿不到它授權規(guī)則會靜默失效。還有一個容易踩的坑如果在項目里自定義了WebMvcConfigurer的addInterceptors方法要注意別覆蓋掉Sentinel的攔截器注冊否則整個Sentinel鏈路都不會生效。另外來源解析和資源名解析是兩回事。資源名解析走的是UrlCleaner用來把/order/create/123清洗成/order/create/*來源解析走的是RequestOriginParser只負責回答“這個請求是誰發(fā)來的”。這兩個邏輯不要混在一起。2.3 控制臺配置步驟與白名單實戰(zhàn)假設現(xiàn)在有一個下單接口/order/create我們希望只允許來源為gateway的請求訪問其他來源一律拒絕。操作很簡單在控制臺左側菜單進入“授權規(guī)則”點“新增授權規(guī)則”資源名填/order/create授權類型選“白名單”來源應用填gateway保存后這條規(guī)則就生效了。規(guī)則底層邏輯等價于AuthorityRule rule new AuthorityRule(); rule.setResource(/order/create); rule.setLimitApp(gateway); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(rule));保存后可以啟動一個請求不帶X-Origin頭去訪問這個接口會看到類似SentinelBlockException: AuthorityException的異常帶上X-Origin: gateway就能正常通過。實踐中我建議同時配置一套黑名單規(guī)則來封殺惡意調用者比如來源標識為spider的請求AuthorityRule blackRule new AuthorityRule(); blackRule.setResource(/order/create); blackRule.setLimitApp(spider); blackRule.setStrategy(RuleConstant.AUTHORITY_BLACK); AuthorityRuleManager.loadRules(Collections.singletonList(blackRule));這里有一個細節(jié)授權規(guī)則的資源名必須和Sentinel埋點的資源名完全一致。Spring MVC適配器默認用請求URI作為資源名所以/order/create這個寫法沒問題如果項目里使用了自定義埋點SphU.entry(createOrder)那授權規(guī)則的資源名就要填createOrder而不是URL。3. 系統(tǒng)規(guī)則拆解五個維度的閾值怎么定、怎么生效系統(tǒng)規(guī)則在控制臺里看上去更簡單一行配置五個可選項。但它的內部機制比授權規(guī)則復雜不少因為涉及機器級指標的采集和入口流量的統(tǒng)計。3.1 五個維度背后的含義系統(tǒng)規(guī)則的五個維度分別是維度參數(shù)名含義典型設置參考LoadhighestSystemLoad系統(tǒng)當前Load1分鐘平均僅Linux等類Unix系統(tǒng)有效核數(shù)的1.5倍到2倍比如4核機器設6.0CPUhighestCpuUsageCPU使用率0.0到1.0之間0.6到0.8留出緩沖RTavgRt所有入口流量的平均響應時間單位毫秒業(yè)務P99耗時的1.5倍左右線程數(shù)maxThreads入口流量的并發(fā)線程數(shù)參考壓測得出的最大并發(fā)數(shù)入口QPSqps所有入口流量的總QPS峰值QPS的70%左右注意這里的RT和線程數(shù)都是“所有入口流量”的聚合指標不是單個接口的指標。設置RT300意味著所有Web請求的平均RT超過300ms就會觸發(fā)全局攔截這在某些長接口業(yè)務里會顯得很激進。比如有一個接口本身要跑5秒但它的調用量很低平均RT只有200ms看起來不會觸發(fā)如果有一天這個慢接口調用量突然上來平均RT被拉高到300ms以上那所有接口都會被攔這其實是合理的——因為整機已經(jīng)被慢調用拖住了。Load維度的判斷邏輯是只有當系統(tǒng)Load超過閾值且當前并發(fā)線程數(shù)超過系統(tǒng)容量時才會觸發(fā)單純Load高但線程數(shù)不高不觸發(fā)。這個設計是防止機器負載瞬間尖峰導致誤殺。在Windows上測試時Load指標拿不到數(shù)據(jù)所以源碼里直接跳過這個維度的判斷也就是說你在Windows上調Load規(guī)則它永遠不會生效。3.2 入口流量的統(tǒng)計機制系統(tǒng)規(guī)則里反復提到“入口流量”這個詞很多人沒細想。Sentinel把流量分為IN和OUT兩種類型Web請求默認是IN微服務內部調用通常也是IN而OUT代表出口調用。系統(tǒng)規(guī)則只統(tǒng)計入口流量底層通過SystemMetricStatistics做滑動窗口聚合默認統(tǒng)計窗口是1秒每5秒檢查一次閾值。在默認的Spring MVC適配器場景下所有被SentinelWebInterceptor攔截的請求都會自動標記為IN并累加到入口統(tǒng)計里。所以如果只配置了入口QPS500那應用上所有Web請求的總QPS超過500就會全部被攔下來。這個行為對單體應用來說很直觀但在微服務架構下要格外小心你開了多個實例每個實例的入口QPS是獨立的控制臺上配置的500是單機值不是集群值。還有一個容易走彎路的地方系統(tǒng)規(guī)則在控制臺里只有“系統(tǒng)規(guī)則”一個入口配置的規(guī)則是掛在默認入口資源Constants.ENTRY_RESOURCE上的。這個默認入口資源可以理解為“應用的總入口”所有自動埋點的請求都會歸到這里。但它也可以被替換Sentinel提供了一個擴展點SystemRulesManager.registerSystemRulesProvider大部分場景用不到只需要知道有這么一個機制就好。3.3 擴展入口規(guī)則與壓測驗證我用一個實際案例來演示系統(tǒng)規(guī)則的完整落地過程。假設有一個4核8G的服務器預期峰值入口QPS在800左右線上要求P99 RT不高于300msCPU不能打滿。先通過一次簡單的壓測拿到數(shù)據(jù)壓測工具設置200并發(fā)跑5分鐘得到平均RT180ms、入口QPS峰值1050、CPU最高81%。這些數(shù)據(jù)就是設置閾值的依據(jù)??刂婆_新增系統(tǒng)規(guī)則配置一組合理閾值SystemRule rule new SystemRule(); rule.setHighestSystemLoad(6.0); rule.setHighestCpuUsage(0.8); rule.setAvgRt(300); rule.setMaxThreads(200); rule.setQps(800); SystemRuleManager.loadRules(Collections.singletonList(rule));保存后再次壓測當入口QPS超過800時新請求開始被拒絕控制臺能看到SystemBlockException的攔截記錄CPU穩(wěn)定在80%以下服務不再響應超時。這里要提醒一下系統(tǒng)規(guī)則的閾值不是越快越好。如果設置得過低比如CPU0.5業(yè)務上稍微有點波動就觸發(fā)全局攔截反而會影響正常流量。設置的原則是“在業(yè)務可接受的延遲范圍內盡量多抗流量”具體數(shù)值要基于壓測數(shù)據(jù)迭代調整。關于代碼方式配置規(guī)則生產(chǎn)環(huán)境有爭議。用控制臺配置的好處是改動即時生效、方便觀察用代碼配置的好處是隨應用啟動一起加載不依賴控制臺連接。我的建議是測試環(huán)境用控制臺快速調參確定閾值后固化到配置文件里用Sentinel的DataSource機制加載避免生產(chǎn)環(huán)境控制臺不穩(wěn)定導致規(guī)則丟失。4. 避坑指南這兩類規(guī)則最容易踩的坑這兩類規(guī)則看著簡單實際用起來問題不少。我整理了在多個項目里真實遇到的五類問題基本可以覆蓋大部分排查場景。4.1 授權規(guī)則配了不生效這是排在第一的高頻問題??刂婆_里規(guī)則明明保存了但請求進不來也出不去心里沒底。查這個問題的順序一般是第一確認有沒有實現(xiàn)RequestOriginParser并且類上標了Component。沒有實現(xiàn)請求來源解析控制臺填的來源應用名稱就永遠匹配不上白名單模式下所有請求都會被拒絕黑名單模式下規(guī)則形同虛設。第二確認請求來源解析返回值和控制臺填寫的值完全一致。解析邏輯里做了大小寫轉換或去空格控制臺填的是Gateway解析返回的是gateway匹配不上。建議解析邏輯只做trim不做其他轉換避免自己給自己挖坑。第三確認Sentinel鏈路真的生效了。檢查過濾器或攔截器有沒有被自定義配置覆蓋最直接的辦法是看請求日志里有沒有Sentinel相關輸出或者故意配一個流控規(guī)則驗證一下基礎鏈路是否正常。4.2 系統(tǒng)規(guī)則在Windows上不生效很多人開發(fā)機是Windows配了Load規(guī)則后發(fā)現(xiàn)壓測怎么都觸發(fā)不了不是規(guī)則寫錯了而是Windows沒有Load這個概念代碼里檢測到不支持就直接跳過了這個維度的判斷。如果要在開發(fā)階段驗證系統(tǒng)規(guī)則用CPU、RT、線程數(shù)、QPS這四個維度Load維度放到Linux測試環(huán)境再驗。另外還要注意CPU使用率的獲取在不同JDK版本下表現(xiàn)有差異遇到CPU閾值始終不觸發(fā)的情況先看機器監(jiān)控里CPU是不是真的超過了閾值有時候虛擬機搶占導致監(jiān)控工具看到的CPU很高但Java層面拿到的數(shù)據(jù)有延遲。4.3 網(wǎng)關場景的來源解析是另一套APISpring Cloud Gateway場景下Sentinel的接入方式不是SentinelWebInterceptor而是SentinelGatewayFilter請求來源解析的注冊方式也完全不同。需要通過GatewayCallbackManager.setRequestOriginParser來注冊Configuration public class GatewayConfiguration { PostConstruct public void init() { GatewayCallbackManager.setRequestOriginParser(exchange - { String origin exchange.getRequest().getHeaders().getFirst(X-Origin); return origin ! null ? origin : default; }); } }很多人在網(wǎng)關場景下依然按Web MVC的方式寫Component實現(xiàn)RequestOriginParser結果授權規(guī)則完全不生效就是因為這套適配器根本不會從Spring容器里去找那個接口。網(wǎng)關場景的資源名默認是路由ID不是URL路徑配置授權規(guī)則時要用路由ID否則也匹配不上。4.4 閾值設置不能只靠感覺系統(tǒng)規(guī)則的五個維度不是所有都要配更建議只配置當前需要的那一兩個維度避免相互干擾。比如只配入口QPS和CPU兩個維度其他維度保持未配置狀態(tài)Sentinel會跳過未配置的判斷。如果五個維度全部配滿可能出現(xiàn)某個維度先觸發(fā)導致其他規(guī)則來不及驗證的情況給排查增加難度。閾值方面常見的錯誤有兩種一種是把入口QPS設成單接口壓測的吞吐量但入口QPS是所有接口的總和二者差好幾倍另一種是把RT設成單個接口的P99但系統(tǒng)規(guī)則統(tǒng)計的是全局平均RT和單接口的延遲不是一個概念。這兩種錯都會導致線上誤觸發(fā)壓測時需要特別區(qū)分。4.5 授權規(guī)則和系統(tǒng)規(guī)則的配合思路最后聊聊這兩類規(guī)則的配合。授權規(guī)則、系統(tǒng)規(guī)則、流控規(guī)則的觸發(fā)順序是授權規(guī)則最先判斷系統(tǒng)規(guī)則次之流控規(guī)則最后。也就是說一個被授權規(guī)則拒絕的請求不會計入系統(tǒng)規(guī)則的入口統(tǒng)計也不會進入流控判斷一個被系統(tǒng)規(guī)則攔截的請求不會再繼續(xù)往下走流控邏輯?;谶@個順序我通常會這樣設計最外層用授權規(guī)則擋住明確不允許的調用方這是第一道閘第二道用系統(tǒng)規(guī)則保護整機資源這是保命閘再往內才是流控、降級、熱點這些精細化規(guī)則管控單個接口的流量形態(tài)。我在實際項目里第一次用系統(tǒng)規(guī)則時把入口QPS設置成了2000結果白天流量稍微一沖就直接全局攔截業(yè)務方半夜打電話來找我。后來把閾值改到壓測峰值的50%再逐步上調用了一周才找到安全值。這個經(jīng)驗是任何規(guī)則上線前都先在壓測環(huán)境壓一輪拿到真實數(shù)據(jù)再填閾值寧可先收緊再放寬也不要一開始就給一個想當然的大數(shù)字。生產(chǎn)環(huán)境里還有個小技巧系統(tǒng)規(guī)則觸發(fā)時會拒絕入口流量但返回給調用方的異常是BlockException如果你統(tǒng)一封裝了全局異常處理器記得把SystemBlockException也處理掉否則調用方拿到的可能是500而不是預期中的提示信息。授權規(guī)則拋出的AuthorityException同理也應該在異常處理里做統(tǒng)一兜底給前端一個明確的提示而不是裸拋異常。這一篇把授權規(guī)則和系統(tǒng)規(guī)則講完Sentinel的規(guī)則體系就補得差不多了。下一篇準備聊規(guī)則持久化和動態(tài)數(shù)據(jù)源這應該是生產(chǎn)環(huán)境里最后一塊拼圖了。