簡(jiǎn)介Sentinel 可以簡(jiǎn)單的分為 Sentinel 核心庫(kù)和 Dashboard。核心庫(kù)不依賴(lài) Dashboard,但是結(jié)合 Dashboard 可以取得最好的效果。 這篇文章主要介紹 Sentinel 核心庫(kù)的使用。如果希望有一個(gè)最快最直接的了解,可以參考 新手指南 來(lái)獲取一個(gè)最直觀的感受。 我們說(shuō)的資源,可以是任何東西,服務(wù),服務(wù)里的方法,甚至是一段代碼。使用 Sentinel 來(lái)進(jìn)行資源保護(hù),主要分為幾個(gè)步驟: 定義資源 定義規(guī)則 檢驗(yàn)規(guī)則是否生效
先把可能需要保護(hù)的資源定義好(埋點(diǎn)),之后再配置規(guī)則。也可以理解為,只要有了資源,我們就可以在任何時(shí)候靈活地定義各種流量控制規(guī)則。在編碼的時(shí)候,只需要考慮這個(gè)代碼是否需要保護(hù),如果需要保護(hù),就將之定義為一個(gè)資源。 對(duì)于主流的框架,我們提供適配,只需要按照適配中的說(shuō)明配置,Sentinel 就會(huì)默認(rèn)定義提供的服務(wù),方法等為資源。
定義資源
方式一:主流框架的默認(rèn)適配為了減少開(kāi)發(fā)的復(fù)雜程度,我們對(duì)大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了適配。您只需要引入對(duì)應(yīng)的依賴(lài)即可方便地整合 Sentinel??梢詤⒁?jiàn): 主流框架的適配。
方式二:拋出異常的方式定義資源SphU 包含了 try-catch 風(fēng)格的 API。用這種方式,當(dāng)資源發(fā)生了限流之后會(huì)拋出 BlockException 。這個(gè)時(shí)候可以捕捉異常,進(jìn)行限流之后的邏輯處理。示例代碼如下:
// 1.5.0 版本開(kāi)始可以利用 try-with-resources 特性(使用有限制) // 資源名可使用任意有業(yè)務(wù)語(yǔ)義的字符串,比如方法名、接口名或其它可唯一標(biāo)識(shí)的字符串。 try (Entry entry = SphU.entry("resourceName")) { // 被保護(hù)的業(yè)務(wù)邏輯 // do something here... } catch (BlockException ex) { // 資源訪問(wèn)阻止,被限流或被降級(jí) // 在此處進(jìn)行相應(yīng)的處理操作 }
特別地,若 entry 的時(shí)候傳入了熱點(diǎn)參數(shù),那么 exit 的時(shí)候也一定要帶上對(duì)應(yīng)的參數(shù)(exit(count, args) ),否則可能會(huì)有統(tǒng)計(jì)錯(cuò)誤。這個(gè)時(shí)候不能使用 try-with-resources 的方式。另外通過(guò) Tracer.trace(ex) 來(lái)統(tǒng)計(jì)異常信息時(shí),由于 try-with-resources 語(yǔ)法中 catch 調(diào)用順序的問(wèn)題,會(huì)導(dǎo)致無(wú)法正確統(tǒng)計(jì)異常數(shù),因此統(tǒng)計(jì)異常信息時(shí)也不能在 try-with-resources 的 catch 塊中調(diào)用 Tracer.trace(ex) 。 手動(dòng) exit 示例: Entry entry = null; // 務(wù)必保證 finally 會(huì)被執(zhí)行 try { // 資源名可使用任意有業(yè)務(wù)語(yǔ)義的字符串,注意數(shù)目不能太多(超過(guò) 1K),超出幾千請(qǐng)作為參數(shù)傳入而不要直接作為資源名 // EntryType 代表流量類(lèi)型(inbound/outbound),其中系統(tǒng)規(guī)則只對(duì) IN 類(lèi)型的埋點(diǎn)生效 entry = SphU.entry("自定義資源名"); // 被保護(hù)的業(yè)務(wù)邏輯 // do something... } catch (BlockException ex) { // 資源訪問(wèn)阻止,被限流或被降級(jí) // 進(jìn)行相應(yīng)的處理操作 } catch (Exception ex) { // 若需要配置降級(jí)規(guī)則,需要通過(guò)這種方式記錄業(yè)務(wù)異常 Tracer.traceEntry(ex, entry); } finally { // 務(wù)必保證 exit,務(wù)必保證每個(gè) entry 與 exit 配對(duì) if (entry != null) { entry.exit(); } }
熱點(diǎn)參數(shù)埋點(diǎn)示例: Entry entry = null; try { // 若需要配置例外項(xiàng),則傳入的參數(shù)只支持基本類(lèi)型。 // EntryType 代表流量類(lèi)型,其中系統(tǒng)規(guī)則只對(duì) IN 類(lèi)型的埋點(diǎn)生效 // count 大多數(shù)情況都填 1,代表統(tǒng)計(jì)為一次調(diào)用。 entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB); // Your logic here. } catch (BlockException ex) { // Handle request rejection. } finally { // 注意:exit 的時(shí)候也一定要帶上對(duì)應(yīng)的參數(shù),否則可能會(huì)有統(tǒng)計(jì)錯(cuò)誤。 if (entry != null) { entry.exit(1, paramA, paramB); } }
SphU.entry() 的參數(shù)描述:
參數(shù)名 | 類(lèi)型 | 解釋 | 默認(rèn)值 |
---|
entryType | EntryType | 資源調(diào)用的流量類(lèi)型,是入口流量(EntryType.IN )還是出口流量(EntryType.OUT ),注意系統(tǒng)規(guī)則只對(duì) IN 生效 | EntryType.OUT | count | int | 本次資源調(diào)用請(qǐng)求的 token 數(shù)目 | 1 | args | Object[] | 傳入的參數(shù),用于熱點(diǎn)參數(shù)限流 | 無(wú) |
注意:SphU.entry(xxx) 需要與 entry.exit() 方法成對(duì)出現(xiàn),匹配調(diào)用,否則會(huì)導(dǎo)致調(diào)用鏈記錄異常,拋出 ErrorEntryFreeException 異常。常見(jiàn)的錯(cuò)誤:
方式三:返回布爾值方式定義資源SphO 提供 if-else 風(fēng)格的 API。用這種方式,當(dāng)資源發(fā)生了限流之后會(huì)返回 false ,這個(gè)時(shí)候可以根據(jù)返回值,進(jìn)行限流之后的邏輯處理。示例代碼如下:
// 資源名可使用任意有業(yè)務(wù)語(yǔ)義的字符串 if (SphO.entry("自定義資源名")) { // 務(wù)必保證finally會(huì)被執(zhí)行 try { /** * 被保護(hù)的業(yè)務(wù)邏輯 */ } finally { SphO.exit(); } } else { // 資源訪問(wèn)阻止,被限流或被降級(jí) // 進(jìn)行相應(yīng)的處理操作 }
注意:SphO.entry(xxx) 需要與 SphO.exit()方法成對(duì)出現(xiàn),匹配調(diào)用,位置正確,否則會(huì)導(dǎo)致調(diào)用鏈記錄異常,拋出 ErrorEntryFreeException` 異常。
方式四:注解方式定義資源Sentinel 支持通過(guò) @SentinelResource 注解定義資源并配置 blockHandler 和 fallback 函數(shù)來(lái)進(jìn)行限流之后的處理。示例: // 原本的業(yè)務(wù)方法. @SentinelResource(blockHandler = "blockHandlerForGetUser") public User getUserById(String id) { throw new RuntimeException("getUserById command failed"); }
// blockHandler 函數(shù),原方法調(diào)用被限流/降級(jí)/系統(tǒng)保護(hù)的時(shí)候調(diào)用 public User blockHandlerForGetUser(String id, BlockException ex) { return new User("admin"); }
注意 blockHandler 函數(shù)會(huì)在原方法被限流/降級(jí)/系統(tǒng)保護(hù)的時(shí)候調(diào)用,而 fallback 函數(shù)會(huì)針對(duì)所有類(lèi)型的異常。請(qǐng)注意 blockHandler 和 fallback 函數(shù)的形式要求,更多指引可以參見(jiàn) Sentinel 注解支持文檔。
方式五:異步調(diào)用支持Sentinel 支持異步調(diào)用鏈路的統(tǒng)計(jì)。在異步調(diào)用中,需要通過(guò) SphU.asyncEntry(xxx) 方法定義資源,并通常需要在異步的回調(diào)函數(shù)中調(diào)用 exit 方法。以下是一個(gè)簡(jiǎn)單的示例: try { AsyncEntry entry = SphU.asyncEntry(resourceName);
// 異步調(diào)用. doAsync(userId, result -> { try { // 在此處處理異步調(diào)用的結(jié)果. } finally { // 在回調(diào)結(jié)束后 exit. entry.exit(); } }); } catch (BlockException ex) { // Request blocked. // Handle the exception (e.g. retry or fallback). }
SphU.asyncEntry(xxx) 不會(huì)影響當(dāng)前(調(diào)用線程)的 Context,因此以下兩個(gè) entry 在調(diào)用鏈上是平級(jí)關(guān)系(處于同一層),而不是嵌套關(guān)系:
// 調(diào)用鏈類(lèi)似于: // -parent // ---asyncResource // ---syncResource asyncEntry = SphU.asyncEntry(asyncResource); entry = SphU.entry(normalResource);
若在異步回調(diào)中需要嵌套其它的資源調(diào)用(無(wú)論是 entry 還是 asyncEntry ),只需要借助 Sentinel 提供的上下文切換功能,在對(duì)應(yīng)的地方通過(guò) ContextUtil.runOnContext(context, f) 進(jìn)行 Context 變換,將對(duì)應(yīng)資源調(diào)用處的 Context 切換為生成的異步 Context,即可維持正確的調(diào)用鏈路關(guān)系。示例如下: public void handleResult(String result) { Entry entry = null; try { entry = SphU.entry("handleResultForAsync"); // Handle your result here. } catch (BlockException ex) { // Blocked for the result handler. } finally { if (entry != null) { entry.exit(); } } }
public void someAsync() { try { AsyncEntry entry = SphU.asyncEntry(resourceName);
// Asynchronous invocation. doAsync(userId, result -> { // 在異步回調(diào)中進(jìn)行上下文變換,通過(guò) AsyncEntry 的 getAsyncContext 方法獲取異步 Context ContextUtil.runOnContext(entry.getAsyncContext(), () -> { try { // 此處嵌套正常的資源調(diào)用. handleResult(result); } finally { entry.exit(); } }); }); } catch (BlockException ex) { // Request blocked. // Handle the exception (e.g. retry or fallback). } }
此時(shí)的調(diào)用鏈就類(lèi)似于: -parent ---asyncInvocation -----handleResultForAsync
更詳細(xì)的示例可以參考 Demo 中的 AsyncEntryDemo,里面包含了普通資源與異步資源之間的各種嵌套示例。
規(guī)則的種類(lèi)Sentinel 的所有規(guī)則都可以在內(nèi)存態(tài)中動(dòng)態(tài)地查詢(xún)及修改,修改之后立即生效。同時(shí) Sentinel 也提供相關(guān) API,供您來(lái)定制自己的規(guī)則策略。 Sentinel 支持以下幾種規(guī)則:流量控制規(guī)則、熔斷降級(jí)規(guī)則、系統(tǒng)保護(hù)規(guī)則、來(lái)源訪問(wèn)控制規(guī)則 和 熱點(diǎn)參數(shù)規(guī)則。
流量控制規(guī)則 (FlowRule)
流量規(guī)則的定義重要屬性: Field | 說(shuō)明 | 默認(rèn)值 |
---|
resource | 資源名,資源名是限流規(guī)則的作用對(duì)象 |
| count | 限流閾值 |
| grade | 限流閾值類(lèi)型,QPS 模式(1)或并發(fā)線程數(shù)模式(0) | QPS 模式 | limitApp | 流控針對(duì)的調(diào)用來(lái)源 | default ,代表不區(qū)分調(diào)用來(lái)源 | strategy | 調(diào)用關(guān)系限流策略:直接、鏈路、關(guān)聯(lián) | 根據(jù)資源本身(直接) | controlBehavior | 流控效果(直接拒絕/WarmUp/勻速+排隊(duì)等待),不支持按調(diào)用關(guān)系限流 | 直接拒絕 | clusterMode | 是否集群限流 | 否 |
同一個(gè)資源可以同時(shí)有多個(gè)限流規(guī)則,檢查規(guī)則時(shí)會(huì)依次檢查。
通過(guò)代碼定義流量控制規(guī)則理解上面規(guī)則的定義之后,我們可以通過(guò)調(diào)用 FlowRuleManager.loadRules() 方法來(lái)用硬編碼的方式定義流量控制規(guī)則,比如: private void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); // set limit qps to 20 rule.setCount(20); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }
更多詳細(xì)內(nèi)容可以參考 流量控制。
熔斷降級(jí)規(guī)則 (DegradeRule)熔斷降級(jí)規(guī)則包含下面幾個(gè)重要的屬性: Field | 說(shuō)明 | 默認(rèn)值 |
---|
resource | 資源名,即規(guī)則的作用對(duì)象 |
| grade | 熔斷策略,支持慢調(diào)用比例/異常比例/異常數(shù)策略 | 慢調(diào)用比例 | count | 慢調(diào)用比例模式下為慢調(diào)用臨界 RT(超出該值計(jì)為慢調(diào)用);異常比例/異常數(shù)模式下為對(duì)應(yīng)的閾值 |
| timeWindow | 熔斷時(shí)長(zhǎng),單位為 s |
| minRequestAmount | 熔斷觸發(fā)的最小請(qǐng)求數(shù),請(qǐng)求數(shù)小于該值時(shí)即使異常比率超出閾值也不會(huì)熔斷(1.7.0 引入) | 5 | statIntervalMs | 統(tǒng)計(jì)時(shí)長(zhǎng)(單位為 ms),如 60*1000 代表分鐘級(jí)(1.8.0 引入) | 1000 ms | slowRatioThreshold | 慢調(diào)用比例閾值,僅慢調(diào)用比例模式有效(1.8.0 引入) |
|
同一個(gè)資源可以同時(shí)有多個(gè)降級(jí)規(guī)則。 理解上面規(guī)則的定義之后,我們可以通過(guò)調(diào)用 DegradeRuleManager.loadRules() 方法來(lái)用硬編碼的方式定義流量控制規(guī)則。 private void initDegradeRule() { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); rule.setResource(KEY); // set threshold RT, 10 ms rule.setCount(10); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); }
更多詳情可以參考 熔斷降級(jí)。
系統(tǒng)保護(hù)規(guī)則 (SystemRule)Sentinel 系統(tǒng)自適應(yīng)限流從整體維度對(duì)應(yīng)用入口流量進(jìn)行控制,結(jié)合應(yīng)用的 Load、CPU 使用率、總體平均 RT、入口 QPS
和并發(fā)線程數(shù)等幾個(gè)維度的監(jiān)控指標(biāo),通過(guò)自適應(yīng)的流控策略,讓系統(tǒng)的入口流量和系統(tǒng)的負(fù)載達(dá)到一個(gè)平衡,讓系統(tǒng)盡可能跑在最大吞吐量的同時(shí)保證系統(tǒng)整體的穩(wěn)定性。 系統(tǒng)規(guī)則包含下面幾個(gè)重要的屬性: Field | 說(shuō)明 | 默認(rèn)值 |
---|
highestSystemLoad | load1 觸發(fā)值,用于觸發(fā)自適應(yīng)控制階段 | -1 (不生效) | avgRt | 所有入口流量的平均響應(yīng)時(shí)間 | -1 (不生效) | maxThread | 入口流量的最大并發(fā)數(shù) | -1 (不生效) | qps | 所有入口資源的 QPS | -1 (不生效) | highestCpuUsage | 當(dāng)前系統(tǒng)的 CPU 使用率(0.0-1.0) | -1 (不生效) |
理解上面規(guī)則的定義之后,我們可以通過(guò)調(diào)用 SystemRuleManager.loadRules() 方法來(lái)用硬編碼的方式定義流量控制規(guī)則。 private void initSystemRule() { List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(10); rules.add(rule); SystemRuleManager.loadRules(rules); }
注意系統(tǒng)規(guī)則只針對(duì)入口資源(EntryType=IN)生效。更多詳情可以參考 系統(tǒng)自適應(yīng)保護(hù)文檔。
訪問(wèn)控制規(guī)則 (AuthorityRule)很多時(shí)候,我們需要根據(jù)調(diào)用方來(lái)限制資源是否通過(guò),這時(shí)候可以使用 Sentinel 的訪問(wèn)控制(黑白名單)的功能。黑白名單根據(jù)資源的請(qǐng)求來(lái)源(origin )限制資源是否通過(guò),若配置白名單則只有請(qǐng)求來(lái)源位于白名單內(nèi)時(shí)才可通過(guò);若配置黑名單則請(qǐng)求來(lái)源位于黑名單時(shí)不通過(guò),其余的請(qǐng)求通過(guò)。 授權(quán)規(guī)則,即黑白名單規(guī)則(AuthorityRule )非常簡(jiǎn)單,主要有以下配置項(xiàng): resource :資源名,即規(guī)則的作用對(duì)象
limitApp :對(duì)應(yīng)的黑名單/白名單,不同 origin 用 , 分隔,如 appA,appB
strategy :限制模式,AUTHORITY_WHITE 為白名單模式,AUTHORITY_BLACK 為黑名單模式,默認(rèn)為白名單模式
更多詳情可以參考 來(lái)源訪問(wèn)控制。
熱點(diǎn)規(guī)則 (ParamFlowRule)詳情可以參考 熱點(diǎn)參數(shù)限流。
查詢(xún)更改規(guī)則引入了 transport 模塊后,可以通過(guò)以下的 HTTP API 來(lái)獲取所有已加載的規(guī)則: http://localhost:8719/getRules?type=<XXXX> 其中,type=flow 以 JSON 格式返回現(xiàn)有的限流規(guī)則,degrade 返回現(xiàn)有生效的降級(jí)規(guī)則列表,system 則返回系統(tǒng)保護(hù)規(guī)則。 獲取所有熱點(diǎn)規(guī)則: http://localhost:8719/getParamRules
定制自己的持久化規(guī)則上面的規(guī)則配置,都是存在內(nèi)存中的。即如果應(yīng)用重啟,這個(gè)規(guī)則就會(huì)失效。因此我們提供了開(kāi)放的接口,您可以通過(guò)實(shí)現(xiàn) DataSource 接口的方式,來(lái)自定義規(guī)則的存儲(chǔ)數(shù)據(jù)源。通常我們的建議有: 更多詳情請(qǐng)參考 動(dòng)態(tài)規(guī)則配置。
規(guī)則生效的效果
判斷限流降級(jí)異常在 Sentinel 中所有流控降級(jí)相關(guān)的異常都是異常類(lèi) BlockException 的子類(lèi): 流控異常:FlowException 熔斷降級(jí)異常:DegradeException 系統(tǒng)保護(hù)異常:SystemBlockException 熱點(diǎn)參數(shù)限流異常:ParamFlowException
我們可以通過(guò)以下函數(shù)判斷是否為 Sentinel 的流控降級(jí)異常: BlockException.isBlockException(Throwable t); 除了在業(yè)務(wù)代碼邏輯上看到規(guī)則生效,我們也可以通過(guò)下面簡(jiǎn)單的方法,來(lái)校驗(yàn)規(guī)則生效的效果: 暴露的 HTTP 接口:通過(guò)運(yùn)行下面命令 curl http://localhost:8719/cnode?id=<資源名稱(chēng)> ,觀察返回的數(shù)據(jù)。如果規(guī)則生效,在返回的數(shù)據(jù)欄中的 block 以及 block(m) 中會(huì)有顯示 日志:Sentinel 提供秒級(jí)的資源運(yùn)行日志以及限流日志,詳情可以參考: 日志
block 事件Sentinel 提供以下擴(kuò)展接口,可以通過(guò) StatisticSlotCallbackRegistry 向 StatisticSlot 注冊(cè)回調(diào)函數(shù): 可以利用這些回調(diào)接口來(lái)實(shí)現(xiàn)報(bào)警等功能,實(shí)時(shí)的監(jiān)控信息可以從 ClusterNode 中實(shí)時(shí)獲取。
其它 API
業(yè)務(wù)異常統(tǒng)計(jì) Tracer業(yè)務(wù)異常記錄類(lèi) Tracer 用于記錄業(yè)務(wù)異常。相關(guān)方法: 如果用戶(hù)通過(guò) SphU 或 SphO 手動(dòng)定義資源,則 Sentinel 不能感知上層業(yè)務(wù)的異常,需要手動(dòng)調(diào)用 Tracer.trace(ex) 來(lái)記錄業(yè)務(wù)異常,否則對(duì)應(yīng)的異常不會(huì)統(tǒng)計(jì)到 Sentinel 異常計(jì)數(shù)中。注意不要在 try-with-resources 形式的 SphU.entry(xxx) 中使用,否則會(huì)統(tǒng)計(jì)不上。 從 1.3.1 版本開(kāi)始,注解方式定義資源支持自動(dòng)統(tǒng)計(jì)業(yè)務(wù)異常,無(wú)需手動(dòng)調(diào)用 Tracer.trace(ex) 來(lái)記錄業(yè)務(wù)異常。Sentinel 1.3.1 以前的版本需要手動(dòng)記錄。
上下文工具類(lèi) ContextUtil相關(guān)靜態(tài)方法: 標(biāo)識(shí)進(jìn)入調(diào)用鏈入口(上下文): 以下靜態(tài)方法用于標(biāo)識(shí)調(diào)用鏈路入口,用于區(qū)分不同的調(diào)用鏈路: public static Context enter(String contextName)
public static Context enter(String contextName, String origin)
其中 contextName 代表調(diào)用鏈路入口名稱(chēng)(上下文名稱(chēng)),origin 代表調(diào)用來(lái)源名稱(chēng)。默認(rèn)調(diào)用來(lái)源為空。返回值類(lèi)型為 Context ,即生成的調(diào)用鏈路上下文對(duì)象。 流控規(guī)則中若選擇“流控方式”為“鏈路”方式,則入口資源名即為上面的 contextName 。 注意: ContextUtil.enter(xxx) 方法僅在調(diào)用鏈路入口處生效,即僅在當(dāng)前線程的初次調(diào)用生效,后面再調(diào)用不會(huì)覆蓋當(dāng)前線程的調(diào)用鏈路,直到 exit。Context 存于 ThreadLocal 中,因此切換線程時(shí)可能會(huì)丟掉,如果需要跨線程使用可以結(jié)合 runOnContext 方法使用。
origin 數(shù)量不要太多,否則內(nèi)存占用會(huì)比較大。
退出調(diào)用鏈(清空上下文): 獲取當(dāng)前線程的調(diào)用鏈上下文: 在某個(gè)調(diào)用鏈上下文中執(zhí)行代碼:
Dashboard詳情請(qǐng)參考:Sentinel Dashboard 文檔。
|