Sentinel 流控規(guī)則詳解
本文轉(zhuǎn)載自微信公眾號(hào)「運(yùn)維開(kāi)發(fā)故事」,作者老鄭。轉(zhuǎn)載本文請(qǐng)聯(lián)系運(yùn)維開(kāi)發(fā)故事公眾號(hào)。
在前面兩篇文章給大家介紹了 Sentinel 的功能和基本使用?,F(xiàn)在我們繼續(xù)來(lái)學(xué)習(xí) Sentinel 控制臺(tái)的基本使用,以及一些規(guī)則配置的說(shuō)明。讓大家能夠在工作中使用 Sentinel 得心應(yīng)手 (大部分理論和描述來(lái)源于官方文檔和網(wǎng)絡(luò))。
在正文開(kāi)始之前,我先說(shuō)一下我的基本環(huán)境信息
- jdk 1.8
- sentinel 1.8.0
- spring-boot 2.3.5.RELEASE
- spring-cloud Hoxton.SR8
- spring-cloud-alibaba 2.2.5.RELEASE
控制臺(tái)簡(jiǎn)介
Sentinel 提供一個(gè)輕量級(jí)的開(kāi)源控制臺(tái),它提供機(jī)器發(fā)現(xiàn)以及健康情況管理、監(jiān)控(單機(jī)和集群),規(guī)則管理和推送的功能。這里,我們將會(huì)詳細(xì)講述如何通過(guò)簡(jiǎn)單的步驟就可以使用這些功能。
Sentinel 控制臺(tái)包含如下功能:
- 查看機(jī)器列表以及健康情況:收集 Sentinel 客戶端發(fā)送的心跳包,用于判斷機(jī)器是否在線。
- 監(jiān)控 (單機(jī)和集群聚合):通過(guò) Sentinel 客戶端暴露的監(jiān)控 API,定期拉取并且聚合應(yīng)用監(jiān)控信息,最終可以實(shí)現(xiàn)秒級(jí)的實(shí)時(shí)監(jiān)控。
- 規(guī)則管理和推送:統(tǒng)一管理推送規(guī)則。
- 鑒權(quán):生產(chǎn)環(huán)境中鑒權(quán)非常重要。這里每個(gè)開(kāi)發(fā)者需要根據(jù)自己的實(shí)際情況進(jìn)行定制。
注意:Sentinel 控制臺(tái)目前僅支持單機(jī)部署。Sentinel 控制臺(tái)項(xiàng)目提供 Sentinel 功能全集示例,不作為開(kāi)箱即用的生產(chǎn)環(huán)境控制臺(tái),若希望在生產(chǎn)環(huán)境使用需要自行定制和改造。
Alibaba 提供了企業(yè)版本的 Sentinel 我們可以在 aliyun.com 上面購(gòu)買 AHAS Sentinel
查看機(jī)器列表以及健康情況
如果我們正確的接入 Sentinel 之后我們可以在 Sentinel 控制臺(tái)的 機(jī)器列表 菜單中來(lái)查看我們服務(wù)節(jié)點(diǎn)的健康情況
如果 Sentinel 接入不成功,可以查閱 Sentinel 官方文檔或者 FAQ 來(lái)對(duì)應(yīng)排查
服務(wù)監(jiān)控
1. 實(shí)時(shí)監(jiān)控
同時(shí),同一個(gè)服務(wù)下的所有機(jī)器的簇點(diǎn)信息會(huì)被匯總,并且秒級(jí)地展示在"實(shí)時(shí)監(jiān)控"下。
注意: 實(shí)時(shí)監(jiān)控僅存儲(chǔ) 5 分鐘以內(nèi)的數(shù)據(jù),如果需要持久化,需要通過(guò)調(diào)用實(shí)時(shí)監(jiān)控接口來(lái)定制。
注意:請(qǐng)確保 Sentinel 控制臺(tái)所在的機(jī)器時(shí)間與自己應(yīng)用的機(jī)器時(shí)間保持一致,否則會(huì)導(dǎo)致拉不到實(shí)時(shí)的監(jiān)控?cái)?shù)據(jù)。
2. 簇點(diǎn)鏈路
簇點(diǎn)鏈路(單機(jī)調(diào)用鏈路)頁(yè)面實(shí)時(shí)的去拉取指定客戶端資源的運(yùn)行情況。它一共提供兩種展示模式:一種用樹(shù)狀結(jié)構(gòu)展示資源的調(diào)用鏈路,另外一種則不區(qū)分調(diào)用鏈路展示資源的實(shí)時(shí)情況。
注意: 簇點(diǎn)鏈路監(jiān)控是內(nèi)存態(tài)的信息,它僅展示啟動(dòng)后調(diào)用過(guò)的資源。
注意:請(qǐng)確保 Sentinel 控制臺(tái)所在的機(jī)器時(shí)間與自己應(yīng)用的機(jī)器時(shí)間保持一致,否則會(huì)導(dǎo)致拉不到實(shí)時(shí)的監(jiān)控?cái)?shù)據(jù)。
3 流控規(guī)則
流量控制(flow control),其原理是監(jiān)控應(yīng)用流量的 QPS 或并發(fā)線程數(shù)等指標(biāo),當(dāng)達(dá)到指定的閾值時(shí)對(duì)流量進(jìn)行控制,以避免被瞬時(shí)的流量高峰沖垮,從而保障應(yīng)用的高可用性。
FlowSlot 會(huì)根據(jù)預(yù)設(shè)的規(guī)則,結(jié)合 NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot 統(tǒng)計(jì)出來(lái)的實(shí)時(shí)信息進(jìn)行流量控制。
限流的直接表現(xiàn)是在執(zhí)行 Entry nodeA = SphU.entry(resourceName) 的時(shí)候拋出 FlowException 異常。FlowException 是 BlockException 的子類,您可以捕捉 BlockException 來(lái)自定義被限流之后的處理邏輯。
Sentinel 在觸發(fā)規(guī)則保護(hù)時(shí),返回的異常頁(yè)面是一樣的。不好區(qū)分是因?yàn)槟姆N規(guī)則導(dǎo)致的異常。所以需要自定義異常返回信息,明確是觸發(fā)了哪種類型的規(guī)則。
- @Component
- public class SentinelBlockHandler implements BlockExceptionHandler {
- @Override
- public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
- BlockException e) throws Exception {
- CommonResult<Void> result = new CommonResult<>();
- if (e instanceof FlowException) {
- result = CommonResult.error(101, "接口限流了");
- } else if (e instanceof DegradeException) {
- result = CommonResult.error(102, "服務(wù)降級(jí)了");
- } else if (e instanceof ParamFlowException) {
- result = CommonResult.error(103, "熱點(diǎn)參數(shù)限流了");
- } else if (e instanceof SystemBlockException) {
- result = CommonResult.error(104, "系統(tǒng)規(guī)則(負(fù)載/...不滿足要求)");
- } else if (e instanceof AuthorityException) {
- result = CommonResult.error(105, "授權(quán)規(guī)則不通過(guò)");
- }
- // http狀態(tài)碼
- httpServletResponse.setStatus(500);
- httpServletResponse.setCharacterEncoding("utf-8");
- httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
- httpServletResponse.setContentType("application/json;charset=utf-8");
- // spring mvc自帶的json操作工具,叫jackson
- new ObjectMapper().writeValue(httpServletResponse.getWriter(), result);
- }
- }
效果如下:
- ➜ curl http://127.0.0.1:8088/getStockDetail
- {"code":1,"message":"this is a success message","data":{"id":1,"code":"STOCK==>1000"}}% ➜ curl http://127.0.0.1:8088/getStockDetail
- {"code":1,"message":"this is a success message","data":{"id":1,"code":"STOCK==>1000"}}% ➜ curl http://127.0.0.1:8088/getStockDetail
- {"code":101,"message":"接口限流了","data":null}%
閾值類型
線程數(shù)
并發(fā)數(shù)控制用于保護(hù)業(yè)務(wù)線程池不被慢調(diào)用耗盡。例如,當(dāng)應(yīng)用所依賴的下游應(yīng)用由于某種原因?qū)е路?wù)不穩(wěn)定、響應(yīng)延遲增加,對(duì)于調(diào)用者來(lái)說(shuō),意味著吞吐量下降和更多的線程數(shù)占用,極端情況下甚至導(dǎo)致線程池耗盡。為應(yīng)對(duì)太多線程占用的情況,業(yè)內(nèi)有使用隔離的方案,比如通過(guò)不同業(yè)務(wù)邏輯使用不同線程池來(lái)隔離業(yè)務(wù)自身之間的資源爭(zhēng)搶(線程池隔離)。這種隔離方案雖然隔離性比較好,但是代價(jià)就是線程數(shù)目太多,線程上下文切換的 overhead 比較大,特別是對(duì)低延時(shí)的調(diào)用有比較大的影響。Sentinel 并發(fā)控制不負(fù)責(zé)創(chuàng)建和管理線程池,而是簡(jiǎn)單統(tǒng)計(jì)當(dāng)前請(qǐng)求上下文的線程數(shù)目(正在執(zhí)行的調(diào)用數(shù)目),如果超出閾值,新的請(qǐng)求會(huì)被立即拒絕,效果類似于信號(hào)量隔離。并發(fā)數(shù)控制通常在調(diào)用端進(jìn)行配置。
可以通過(guò)線程池模擬客戶端調(diào)用, 也可以通過(guò) Jmeter 模擬,觸發(fā)流控的結(jié)果如下:
- ➜ ~ curl http://127.0.0.1:8088/getStockDetail
- {"code":101,"message":"接口限流了","data":null}%
流控模式
調(diào)用關(guān)系包括調(diào)用方、被調(diào)用方;一個(gè)方法又可能會(huì)調(diào)用其它方法,形成一個(gè)調(diào)用鏈路的層次關(guān)系。
直接
當(dāng)資源觸發(fā)流控規(guī)則過(guò)后直接,拋出異常信息
- ➜ ~ curl http://127.0.0.1:8088/getStockDetail
- {"code":101,"message":"接口限流了","data":null}%
關(guān)聯(lián)
當(dāng)兩個(gè)資源之間具有資源爭(zhēng)搶或者依賴關(guān)系的時(shí)候,這兩個(gè)資源便具有了關(guān)聯(lián)。比如對(duì)數(shù)據(jù)庫(kù)同一個(gè)字段的讀操作和寫(xiě)操作存在爭(zhēng)搶,讀的速度過(guò)高會(huì)影響寫(xiě)的速度,寫(xiě)的速度過(guò)高會(huì)影響讀的速度。如果放任讀寫(xiě)操作爭(zhēng)搶資源,則爭(zhēng)搶本身帶來(lái)的開(kāi)銷會(huì)降低整體的吞吐量??墒褂藐P(guān)聯(lián)限流來(lái)避免具有關(guān)聯(lián)關(guān)系的資源之間過(guò)度的爭(zhēng)搶,舉例來(lái)說(shuō),read_db 和 write_db 這兩個(gè)資源分別代表數(shù)據(jù)庫(kù)讀寫(xiě),我們可以給 read_db 設(shè)置限流規(guī)則來(lái)達(dá)到寫(xiě)優(yōu)先的目的:設(shè)置 strategy 為 RuleConstant.STRATEGY_RELATE 同時(shí)設(shè)置 refResource 為 write_db。這樣當(dāng)寫(xiě)庫(kù)操作過(guò)于頻繁時(shí),讀數(shù)據(jù)的請(qǐng)求會(huì)被限流。
如果配置流控規(guī)則為關(guān)聯(lián)模式,那么當(dāng) /hello 接口超過(guò)閾值過(guò)后,就會(huì)對(duì) /getStockDetail 接口觸發(fā)流控規(guī)則。
鏈路
NodeSelectorSlot 中記錄了資源之間的調(diào)用鏈路,這些資源通過(guò)調(diào)用關(guān)系,相互之間構(gòu)成一棵調(diào)用樹(shù)。這棵樹(shù)的根節(jié)點(diǎn)是一個(gè)名字為 machine-root 的虛擬節(jié)點(diǎn),調(diào)用鏈的入口都是這個(gè)虛節(jié)點(diǎn)的子節(jié)點(diǎn)。
一棵典型的調(diào)用樹(shù)如下圖所示:
- machine-root
- / \
- / \
- Entrance1 Entrance2
- / \
- / \
- DefaultNode(nodeA) DefaultNode(nodeA)
上圖中來(lái)自入口 Entrance1 和 Entrance2 的請(qǐng)求都調(diào)用到了資源 NodeA,Sentinel 允許只根據(jù)某個(gè)入口的統(tǒng)計(jì)信息對(duì)資源限流。比如我們可以設(shè)置 strategy 為 RuleConstant.STRATEGY_CHAIN,同時(shí)設(shè)置 refResource 為 Entrance1 來(lái)表示只有從入口 Entrance1 的調(diào)用才會(huì)記錄到 NodeA 的限流統(tǒng)計(jì)當(dāng)中,而不關(guān)心經(jīng) Entrance2 到來(lái)的調(diào)用。
調(diào)用鏈的入口(上下文)是通過(guò) API 方法 ContextUtil.enter(contextName) 定義的,其中 contextName 即對(duì)應(yīng)調(diào)用鏈路入口名稱。詳情可以參考 ContextUtil 文檔。]
測(cè)試會(huì)發(fā)現(xiàn) 鏈路 不會(huì)生效
從1.6.3版本開(kāi)始,Sentinel Web filter默認(rèn)收斂所有URL的入口context,因此鏈路限流不生效。1.7.0版本開(kāi)始(對(duì)應(yīng)SCA 2.1.1.RELEASE),我們?cè)贑ommonFilter引入了WEB_CONTEXT_UNIFY這個(gè)init parameter,用于控制是否收斂context。將其配置為false即可根據(jù)不同的URL進(jìn)行鏈路限流。參考:https://github.com/alibaba/sentinel/issues/1213
解決方案:
1.7.0 版本開(kāi)始(對(duì)應(yīng)Spring Cloud Alibaba的2.1.1.RELEASE) 需要新增依賴
- @Configuration
- public class FilterContextConfig {
- @Bean
- public FilterRegistrationBean sentinelFilterRegistration() {
- FilterRegistrationBean registration = new FilterRegistrationBean();
- registration.setFilter(new CommonFilter());
- registration.addUrlPatterns("/*");
- // 入口資源關(guān)閉聚合
- registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
- registration.setName("sentinelFilter");
- registration.setOrder(1);
- return registration;
- }
- }
然后我們?cè)賴L試觸發(fā)流控規(guī)則, 對(duì) /getStockDetail 進(jìn)行訪問(wèn),這里返回了FlowException
默認(rèn)情況會(huì)返回
如果我們使用 OpenFeign 不添加 fallbackFactory 就會(huì)返回500 , 如果我們添加了就可以避免這個(gè)問(wèn)題。
- // Controller
- @Autowired
- private StockFeign stockFeign;
- @GetMapping("/getStockDetail")
- public CommonResult<StockModel> getStockDetail() {
- CommonResult<StockModel> result = stockFeign.getStockDetail();
- if (result.getCode() != 1) {
- return CommonResult.error(null, result.getCode(), result.getMessage());
- }
- return result;
- }
- // FeignClient
- @FeignClient(name = "stock-service")
- //, fallbackFactory = StockFeignFallbackFactory.class)
- public interface StockFeign {
- @GetMapping("/getStockDetail")
- CommonResult<StockModel> getStockDetail();
- }
Sentinel 部分源碼:
所以,我們?cè)谠O(shè)置鏈路流控規(guī)則的時(shí)候一定要設(shè)置 fallbackFactory。不然無(wú)法處理 FlowExecption 異常信息,造成系統(tǒng)出錯(cuò)。對(duì)于個(gè)人而言,Sentinel 的鏈路規(guī)則比不是特別的好用,無(wú)特殊要求,不建議使用,或者選擇Sentinel 的收費(fèi)版本 AHAS
流控效果
當(dāng) QPS、線程數(shù)超過(guò)某個(gè)閾值的時(shí)候,則采取措施進(jìn)行流量控制。流量控制的效果包括以下幾種:直接拒絕、Warm Up、勻速排隊(duì)。
直接拒絕
直接拒絕(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默認(rèn)的流量控制方式,當(dāng)QPS超過(guò)任意規(guī)則的閾值后,新的請(qǐng)求就會(huì)被立即拒絕,拒絕方式為拋出FlowException。 這種方式適用于對(duì)系統(tǒng)處理能力確切已知的情況下,比如通過(guò)壓測(cè)確定了系統(tǒng)的準(zhǔn)確水位時(shí)。
Warm up
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即預(yù)熱/冷啟動(dòng)方式。當(dāng)系統(tǒng)長(zhǎng)期處于低水位的情況下,當(dāng)流量突然增加時(shí),直接把系統(tǒng)拉升到高水位可能瞬間把系統(tǒng)壓垮。通過(guò)"冷啟動(dòng)",讓通過(guò)的流量緩慢增加,在一定時(shí)間內(nèi)逐漸增加到閾值上限,給冷系統(tǒng)一個(gè)預(yù)熱的時(shí)間,避免冷系統(tǒng)被壓垮。
通常冷啟動(dòng)的過(guò)程系統(tǒng)允許通過(guò)的 QPS 曲線如下圖所示:
默認(rèn) coldFactor 為 3,即請(qǐng)求 QPS 從 threshold / 3 開(kāi)始,經(jīng)預(yù)熱時(shí)長(zhǎng)逐漸升至設(shè)定的 QPS 閾值。
規(guī)則設(shè)置如下圖所示:
通過(guò) Jmeter 請(qǐng)求過(guò)后,可以看到如下效果,完成流控
勻速排隊(duì)
勻速排隊(duì)(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式會(huì)嚴(yán)格控制請(qǐng)求通過(guò)的間隔時(shí)間,也即是讓請(qǐng)求以均勻的速度通過(guò),對(duì)應(yīng)的是漏桶算法。
這種方式主要用于處理間隔性突發(fā)的流量,例如消息隊(duì)列。想象一下這樣的場(chǎng)景,在某一秒有大量的請(qǐng)求到來(lái),而接下來(lái)的幾秒則處于空閑狀態(tài),我們希望系統(tǒng)能夠在接下來(lái)的空閑期間逐漸處理這些請(qǐng)求,而不是在第一秒直接拒絕多余的請(qǐng)求。
注意:勻速排隊(duì)模式暫時(shí)不支持 QPS > 1000 的場(chǎng)景。
規(guī)則設(shè)置如下圖所示:
然后我們通過(guò) jmeter 請(qǐng)求過(guò)后可以看到如下效果:
4. 降級(jí)規(guī)則
流量控制以外,對(duì)調(diào)用鏈路中不穩(wěn)定的資源進(jìn)行熔斷降級(jí)也是保障高可用的重要措施之一。一個(gè)服務(wù)常常會(huì)調(diào)用別的模塊,可能是另外的一個(gè)遠(yuǎn)程服務(wù)、數(shù)據(jù)庫(kù),或者第三方 API 等。例如,支付的時(shí)候,可能需要遠(yuǎn)程調(diào)用銀聯(lián)提供的 API;查詢某個(gè)商品的價(jià)格,可能需要進(jìn)行數(shù)據(jù)庫(kù)查詢。然而,這個(gè)被依賴服務(wù)的穩(wěn)定性是不能保證的。如果依賴的服務(wù)出現(xiàn)了不穩(wěn)定的情況,請(qǐng)求的響應(yīng)時(shí)間變長(zhǎng),那么調(diào)用服務(wù)的方法的響應(yīng)時(shí)間也會(huì)變長(zhǎng),線程會(huì)產(chǎn)生堆積,最終可能耗盡業(yè)務(wù)自身的線程池,服務(wù)本身也變得不可用。
熔斷降級(jí)策略
Sentinel 提供以下幾種熔斷策略:
- 慢調(diào)用比例 (SLOW_REQUEST_RATIO):選擇以慢調(diào)用比例作為閾值,需要設(shè)置允許的慢調(diào)用 RT(即最大的響應(yīng)時(shí)間),請(qǐng)求的響應(yīng)時(shí)間大于該值則統(tǒng)計(jì)為慢調(diào)用。當(dāng)單位統(tǒng)計(jì)時(shí)長(zhǎng)(statIntervalMs)內(nèi)請(qǐng)求數(shù)目大于設(shè)置的最小請(qǐng)求數(shù)目,并且慢調(diào)用的比例大于閾值,則接下來(lái)的熔斷時(shí)長(zhǎng)內(nèi)請(qǐng)求會(huì)自動(dòng)被熔斷。經(jīng)過(guò)熔斷時(shí)長(zhǎng)后熔斷器會(huì)進(jìn)入探測(cè)恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來(lái)的一個(gè)請(qǐng)求響應(yīng)時(shí)間小于設(shè)置的慢調(diào)用 RT 則結(jié)束熔斷,若大于設(shè)置的慢調(diào)用 RT 則會(huì)再次被熔斷。
我們可以在控制臺(tái)配置:
jmeter 模擬請(qǐng)求
異常比例 (ERROR_RATIO):當(dāng)單位統(tǒng)計(jì)時(shí)長(zhǎng)(statIntervalMs)內(nèi)請(qǐng)求數(shù)目大于設(shè)置的最小請(qǐng)求數(shù)目,并且異常的比例大于閾值,則接下來(lái)的熔斷時(shí)長(zhǎng)內(nèi)請(qǐng)求會(huì)自動(dòng)被熔斷。經(jīng)過(guò)熔斷時(shí)長(zhǎng)后熔斷器會(huì)進(jìn)入探測(cè)恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來(lái)的一個(gè)請(qǐng)求成功完成(沒(méi)有錯(cuò)誤)則結(jié)束熔斷,否則會(huì)再次被熔斷。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。
我們可以在控制臺(tái)配置:
- 異常數(shù) (ERROR_COUNT):當(dāng)單位統(tǒng)計(jì)時(shí)長(zhǎng)內(nèi)的異常數(shù)目超過(guò)閾值之后會(huì)自動(dòng)進(jìn)行熔斷。經(jīng)過(guò)熔斷時(shí)長(zhǎng)后熔斷器會(huì)進(jìn)入探測(cè)恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來(lái)的一個(gè)請(qǐng)求成功完成(沒(méi)有錯(cuò)誤)則結(jié)束熔斷,否則會(huì)再次被熔斷。
我們可以在控制臺(tái)配置:
熔斷降級(jí)說(shuō)明
熔斷降級(jí)規(guī)則(DegradeRule)包含下面幾個(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 引入) |
5. 熱點(diǎn)規(guī)則
何為熱點(diǎn)?熱點(diǎn)即經(jīng)常訪問(wèn)的數(shù)據(jù)。很多時(shí)候我們希望統(tǒng)計(jì)某個(gè)熱點(diǎn)數(shù)據(jù)中訪問(wèn)頻次最高的 Top K 數(shù)據(jù),并對(duì)其訪問(wèn)進(jìn)行限制。比如:
- 商品 ID 為參數(shù),統(tǒng)計(jì)一段時(shí)間內(nèi)最常購(gòu)買的商品 ID 并進(jìn)行限制
- 用戶 ID 為參數(shù),針對(duì)一段時(shí)間內(nèi)頻繁訪問(wèn)的用戶 ID 進(jìn)行限制
熱點(diǎn)參數(shù)限流會(huì)統(tǒng)計(jì)傳入?yún)?shù)中的熱點(diǎn)參數(shù),并根據(jù)配置的限流閾值與模式,對(duì)包含熱點(diǎn)參數(shù)的資源調(diào)用進(jìn)行限流。熱點(diǎn)參數(shù)限流可以看做是一種特殊的流量控制,僅對(duì)包含熱點(diǎn)參數(shù)的資源調(diào)用生效。
Sentinel 利用 LRU 策略統(tǒng)計(jì)最近最常訪問(wèn)的熱點(diǎn)參數(shù),結(jié)合令牌桶算法來(lái)進(jìn)行參數(shù)級(jí)別的流控。熱點(diǎn)參數(shù)限流支持集群模式。
熱點(diǎn)規(guī)則配置需要注意:
1. 首先資源必須是通過(guò) @SentinelResource 申明
2. 參數(shù)類型必須是基礎(chǔ)數(shù)據(jù)類型, 否則配置無(wú)效
熱點(diǎn)規(guī)則配置如下圖所示:
注意:資源名稱要和 @SentinelResource 中的資源名稱對(duì)應(yīng)才能生效
控制器類的代碼如下所示:
- @SentinelResource(value = "ResOrderGet",
- fallback = "fallback",
- fallbackClass = SentinelExceptionHandler.class,
- blockHandler = "blockHandler",
- blockHandlerClass = SentinelExceptionHandler.class
- )
- @GetMapping("/order/get/{id}")
- public CommonResult<StockModel> getStockDetails(@PathVariable Integer id) {
- StockModel stockModel = new StockModel();
- stockModel.setCode("STOCK==>1000");
- stockModel.setId(id);
- return CommonResult.success(stockModel);
- }
- // 異常處理類
- public class SentinelResourceExceptionHandler {
- //限流熔斷業(yè)務(wù)邏輯
- public static CommonResult<StockModel> blockHandler(@PathVariable Integer id) {
- return CommonResult.error(null, -100, "系統(tǒng)錯(cuò)誤 (限流熔斷業(yè)務(wù)邏輯)");
- }
- //異常降級(jí)業(yè)務(wù)邏輯
- public static CommonResult<StockModel> fallback(@PathVariable Integer id) {
- return CommonResult.error(null, -100, "系統(tǒng)錯(cuò)誤 (異常降級(jí)業(yè)務(wù)邏輯)");
- }
- }
返回異常信息:
6 授權(quán)規(guī)則
很多時(shí)候,我們需要根據(jù)調(diào)用來(lái)源來(lái)判斷該次請(qǐng)求是否允許放行,這時(shí)候可以使用 Sentinel 的來(lái)源訪問(wèn)控制(黑白名單控制)的功能。來(lái)源訪問(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ò)。
調(diào)用方信息通過(guò) ContextUtil.enter(resourceName, origin) 方法中的 origin 參數(shù)傳入。
Sentinel提供了 RequestOriginParser 接口來(lái)處理訪問(wèn)來(lái)源,Sentinel保護(hù)的資源如果被訪問(wèn),就會(huì)調(diào)用 RequestOriginParser解析訪問(wèn)來(lái)源。
- // 注意導(dǎo)包
- import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
- import javax.servlet.http.HttpServletRequest;
- public class SentinelRequestOriginParser implements RequestOriginParser {
- @Override
- public String parseOrigin(HttpServletRequest request) {
- return request.getParameter("origin");
- }
- }
修改 Config 配置信息
- @Configuration
- public class FilterContextConfig {
- @Bean
- public FilterRegistrationBean sentinelFilterRegistration() {
- FilterRegistrationBean registration = new FilterRegistrationBean();
- registration.setFilter(new CommonFilter());
- registration.addUrlPatterns("/*");
- // 入口資源關(guān)閉聚合
- registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
- registration.setName("sentinelFilter");
- registration.setOrder(1);
- // CommonFilter 的 BlockException 自定義處理邏輯
- WebCallbackManager.setUrlBlockHandler(new SentinelFlowHandler());
- //解決授權(quán)規(guī)則不生效的問(wèn)題
- //com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser
- WebCallbackManager.setRequestOriginParser(new SentinelRequestOriginParser());
- return registration;
- }
- }
規(guī)則配置
執(zhí)行請(qǐng)求
正常通過(guò)
異常不通過(guò)
7 系統(tǒng)規(guī)則
系統(tǒng)保護(hù)規(guī)則是從應(yīng)用級(jí)別的入口流量進(jìn)行控制,從單臺(tái)機(jī)器的 load、CPU 使用率、平均 RT、入口 QPS 和并發(fā)線程數(shù)等幾個(gè)維度監(jiān)控應(yīng)用指標(biāo),讓系統(tǒng)盡可能跑在最大吞吐量的同時(shí)保證系統(tǒng)整體的穩(wěn)定性。
系統(tǒng)保護(hù)規(guī)則是應(yīng)用整體維度的,而不是資源維度的,并且僅對(duì)入口流量生效。入口流量指的是進(jìn)入應(yīng)用的流量(EntryType.IN),比如 Web 服務(wù)或 Dubbo 服務(wù)端接收的請(qǐng)求,都屬于入口流量。
系統(tǒng)規(guī)則支持以下的模式:
- Load 自適應(yīng)(僅對(duì) Linux/Unix-like 機(jī)器生效):系統(tǒng)的 load1 作為啟發(fā)指標(biāo),進(jìn)行自適應(yīng)系統(tǒng)保護(hù)。當(dāng)系統(tǒng) load1 超過(guò)設(shè)定的啟發(fā)值,且系統(tǒng)當(dāng)前的并發(fā)線程數(shù)超過(guò)估算的系統(tǒng)容量時(shí)才會(huì)觸發(fā)系統(tǒng)保護(hù)(BBR 階段)。系統(tǒng)容量由系統(tǒng)的 maxQps * minRt 估算得出。設(shè)定參考值一般是 CPU cores * 2.5。
- CPU usage(1.5.0+ 版本):當(dāng)系統(tǒng) CPU 使用率超過(guò)閾值即觸發(fā)系統(tǒng)保護(hù)(取值范圍 0.0-1.0),比較靈敏。
- 平均 RT:當(dāng)單臺(tái)機(jī)器上所有入口流量的平均 RT 達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù),單位是毫秒。
- 并發(fā)線程數(shù):當(dāng)單臺(tái)機(jī)器上所有入口流量的并發(fā)線程數(shù)達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù)。
- 入口 QPS:當(dāng)單臺(tái)機(jī)器上所有入口流量的 QPS 達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù)。
原理
如下圖所示
我們把系統(tǒng)處理請(qǐng)求的過(guò)程想象為一個(gè)水管,到來(lái)的請(qǐng)求是往這個(gè)水管灌水,當(dāng)系統(tǒng)處理順暢的時(shí)候,請(qǐng)求不需要排隊(duì),直接從水管中穿過(guò),這個(gè)請(qǐng)求的RT是最短的;反之,當(dāng)請(qǐng)求堆積的時(shí)候,那么處理請(qǐng)求的時(shí)間則會(huì)變?yōu)椋号抨?duì)時(shí)間 + 最短處理時(shí)間。
- 推論一: 如果我們能夠保證水管里的水量,能夠讓水順暢的流動(dòng),則不會(huì)增加排隊(duì)的請(qǐng)求;也就是說(shuō),這個(gè)時(shí)候的系統(tǒng)負(fù)載不會(huì)進(jìn)一步惡化。
我們用 T 來(lái)表示(水管內(nèi)部的水量),用RT來(lái)表示請(qǐng)求的處理時(shí)間,用P來(lái)表示進(jìn)來(lái)的請(qǐng)求數(shù),那么一個(gè)請(qǐng)求從進(jìn)入水管道到從水管出來(lái),這個(gè)水管會(huì)存在 P * RT 個(gè)請(qǐng)求。換一句話來(lái)說(shuō),當(dāng) T ≈ QPS * Avg(RT) 的時(shí)候,我們可以認(rèn)為系統(tǒng)的處理能力和允許進(jìn)入的請(qǐng)求個(gè)數(shù)達(dá)到了平衡,系統(tǒng)的負(fù)載不會(huì)進(jìn)一步惡化。
接下來(lái)的問(wèn)題是,水管的水位是可以達(dá)到了一個(gè)平衡點(diǎn),但是這個(gè)平衡點(diǎn)只能保證水管的水位不再繼續(xù)增高,但是還面臨一個(gè)問(wèn)題,就是在達(dá)到平衡點(diǎn)之前,這個(gè)水管里已經(jīng)堆積了多少水。如果之前水管的水已經(jīng)在一個(gè)量級(jí)了,那么這個(gè)時(shí)候系統(tǒng)允許通過(guò)的水量可能只能緩慢通過(guò),RT會(huì)大,之前堆積在水管里的水會(huì)滯留;反之,如果之前的水管水位偏低,那么又會(huì)浪費(fèi)了系統(tǒng)的處理能力。
- 推論二: 當(dāng)保持入口的流量是水管出來(lái)的流量的最大的值的時(shí)候,可以最大利用水管的處理能力。
然而,和 TCP BBR 的不一樣的地方在于,還需要用一個(gè)系統(tǒng)負(fù)載的值(load1)來(lái)激發(fā)這套機(jī)制啟動(dòng)。
注:這種系統(tǒng)自適應(yīng)算法對(duì)于低 load 的請(qǐng)求,它的效果是一個(gè)“兜底”的角色。對(duì)于不是應(yīng)用本身造成的 load 高的情況(如其它進(jìn)程導(dǎo)致的不穩(wěn)定的情況),效果不明顯。
配置頁(yè)面
觸發(fā)流控規(guī)則
8 集群流控
為什么要使用集群流控呢?假設(shè)我們希望給某個(gè)用戶限制調(diào)用某個(gè) API 的總 QPS 為 50,但機(jī)器數(shù)可能很多(比如有 100 臺(tái))。這時(shí)候我們很自然地就想到,找一個(gè) server 來(lái)專門來(lái)統(tǒng)計(jì)總的調(diào)用量,其它的實(shí)例都與這臺(tái) server 通信來(lái)判斷是否可以調(diào)用。這就是最基礎(chǔ)的集群流控的方式。
另外集群流控還可以解決流量不均勻?qū)е驴傮w限流效果不佳的問(wèn)題。假設(shè)集群中有 10 臺(tái)機(jī)器,我們給每臺(tái)機(jī)器設(shè)置單機(jī)限流閾值為 10 QPS,理想情況下整個(gè)集群的限流閾值就為 100 QPS。不過(guò)實(shí)際情況下流量到每臺(tái)機(jī)器可能會(huì)不均勻,會(huì)導(dǎo)致總量沒(méi)有到的情況下某些機(jī)器就開(kāi)始限流。因此僅靠單機(jī)維度去限制的話會(huì)無(wú)法精確地限制總體流量。而集群流控可以精確地控制整個(gè)集群的調(diào)用總量,結(jié)合單機(jī)限流兜底,可以更好地發(fā)揮流量控制的效果。
集群流控中共有兩種身份:
- Token Client:集群流控客戶端,用于向所屬 Token Server 通信請(qǐng)求 token。集群限流服務(wù)端會(huì)返回給客戶端結(jié)果,決定是否限流。
- Token Server:即集群流控服務(wù)端,處理來(lái)自 Token Client 的請(qǐng)求,根據(jù)配置的集群規(guī)則判斷是否應(yīng)該發(fā)放 token(是否允許通過(guò))。
規(guī)則推送
Sentinel 控制臺(tái)同時(shí)提供簡(jiǎn)單的規(guī)則管理以及推送的功能。規(guī)則推送分為 3 種模式,包括 "原始模式"、"Pull 模式" 和"Push 模式"。
這里先簡(jiǎn)單的介紹"原始模式"。
規(guī)則管理
您可以在控制臺(tái)通過(guò)接入端暴露的 HTTP API 來(lái)查詢規(guī)則。
規(guī)則推送
目前控制臺(tái)的規(guī)則推送也是通過(guò) 規(guī)則查詢更改 HTTP API 來(lái)更改規(guī)則。這也意味著這些規(guī)則僅在內(nèi)存態(tài)生效,應(yīng)用重啟之后,該規(guī)則會(huì)丟失。
注:若通過(guò)控制臺(tái)推送規(guī)則時(shí)出現(xiàn) invalid type 或 empty type 的錯(cuò)誤,請(qǐng)確保 transport 模塊版本與 core 模塊版本保持一致;若控制臺(tái)版本 >= 1.7.1,請(qǐng)將接入端的相關(guān)依賴也升級(jí)至 1.7.1 及以上版本。
以上是原始模式。當(dāng)了解了原始模式之后,我們非常鼓勵(lì)您通過(guò) 動(dòng)態(tài)規(guī)則 并結(jié)合各種外部存儲(chǔ)來(lái)定制自己的規(guī)則源。我們推薦通過(guò)動(dòng)態(tài)配置源的控制臺(tái)來(lái)進(jìn)行規(guī)則寫(xiě)入和推送,而不是通過(guò) Sentinel 客戶端直接寫(xiě)入到動(dòng)態(tài)配置源中。在生產(chǎn)環(huán)境中,我們推薦 push 模式,具體可以參考:在生產(chǎn)環(huán)境使用 Sentinel。
注:若要使用集群流控功能,則必須對(duì)接動(dòng)態(tài)規(guī)則源,否則無(wú)法正常使用。您也可以接入 AHAS Sentinel 快速接入全自動(dòng)托管、高可用的集群流控能力。
Sentinel 同時(shí)還提供應(yīng)用維度規(guī)則推送的示例頁(yè)面(流控規(guī)則頁(yè)面,前端路由為 /v2/flow),用戶改造控制臺(tái)對(duì)接配置中心后可直接通過(guò) v2 頁(yè)面推送規(guī)則至配置中心。Sentinel 抽取了通用接口用于向遠(yuǎn)程配置中心推送規(guī)則以及拉取規(guī)則:
DynamicRuleProvider
DynamicRulePublisher
用戶只需實(shí)現(xiàn) DynamicRuleProvider 和 DynamicRulePublisher 接口,并在 v2 的 controller 中通過(guò) @Qualifier 注解替換相應(yīng)的 bean 即可實(shí)現(xiàn)應(yīng)用維度推送。我們提供了 Nacos 和 Apollo 的示例,改造詳情可參考 應(yīng)用維度規(guī)則推送示例。
鑒權(quán)
從 Sentinel 1.5.0 開(kāi)始,控制臺(tái)提供通用的鑒權(quán)接口 AuthService,用戶可根據(jù)需求自行實(shí)現(xiàn)。
從 Sentinel 1.6.0 起,Sentinel 控制臺(tái)引入基本的登錄功能,默認(rèn)用戶名和密碼都是 sentinel。該鑒權(quán)能力非?;A(chǔ),生產(chǎn)環(huán)境使用建議根據(jù)安全需要自行改造。
用戶可以通過(guò)如下參數(shù)進(jìn)行配置:
- -Dsentinel.dashboard.auth.username=sentinel 用于指定控制臺(tái)的登錄用戶名為 sentinel;
- -Dsentinel.dashboard.auth.password=123456 用于指定控制臺(tái)的登錄密碼為 123456;如果省略這兩個(gè)參數(shù),默認(rèn)用戶和密碼均為 sentinel;
- -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服務(wù)端 session 的過(guò)期時(shí)間,如 7200 表示 7200 秒;60m 表示 60 分鐘,默認(rèn)為 30 分鐘;
同樣也可以直接在 Spring properties 文件中進(jìn)行配置。
注意:部署多臺(tái)控制臺(tái)時(shí),session 默認(rèn)不會(huì)在各實(shí)例之間共享,這一塊需要自行改造。
參考文檔
https://github.com/alibaba/Sentinel/wiki/控制臺(tái)