《吃透微服務》 - 服務容錯之Sentinel
大家好,我是小菜。一個希望能夠成為 吹著牛X談架構 的男人!如果你也想成為我想成為的人,不然點個關注做個伴,讓成長道路不再孤單!
本文主要介紹
SpringCloud中Sentinel如有需要,可以參考
如有幫助,不忘 點贊 ?
微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!
上篇我們已經(jīng)了解到微服務中重要的組件之一 --- 服務網(wǎng)關Gateway 。我們在取精排糠的同時,不可否認微服務給我們帶來的好處。其中承載高并發(fā)的好處更是讓各大公司趨之若鶩!
但是想要接收高并發(fā),自然要接收它帶來的一系列問題。在微服務架構中,我們將業(yè)務拆分成了一個個服務,這樣的好處之一便是分擔壓力,一個服務頂不住了,下一個服務頂上去。但是服務與服務之間可以相互調(diào)用,由于網(wǎng)絡原因或自身的原因,服務并不能保證百分百可用,也就是各大公司現(xiàn)在追尋的幾個9(99.99%,99.999%)可用!如果單個服務出現(xiàn)問題,調(diào)用這個服務就會出現(xiàn)網(wǎng)絡延遲,此時如果正好有大量的網(wǎng)絡涌入,勢必會形成任務堆積,導致服務癱瘓!
空口無憑,小菜給你整個示例:
OrderController

這里我們通過接口模擬了下單的場景,其中通過線程休眠的方式模擬了網(wǎng)絡延遲的場景。接下來我們還需要改一下 tomcat 中并發(fā)的線程數(shù)
applicatio.yaml
server:
??tomcat:
????max-threads:?10??#?默認的并發(fā)數(shù)為10
當這一切準備就緒好,我們這個時候還需要壓測工具 Jmeter 的幫助(不會操作的同學具體看以下使用)
- 首先打開Jmeter軟件,選擇新建線程組

- 設置請求線程數(shù)

- 設置HTTP請求取樣器

- 設置請求的接口

完成上面步驟就可以點擊開始了。不過測試之前我們確保兩個API都是可以訪問的:
image-20210612220817883
然后開始壓力測試,當大量請求發(fā)送到創(chuàng)建訂單的接口時,我們這時候通過網(wǎng)頁訪問 detail API 發(fā)現(xiàn)請求一直在阻塞,過一會才聯(lián)通!

這無疑是一個開發(fā)炸彈,而這便是高并發(fā)帶來的問題。看到這里,恭喜你成功見證了一場服務雪崩的問題。那不妨帶著這份興趣繼續(xù)往下看,會給你驚喜的。
Sentinel
一、服務雪崩
我們開頭直接用服務雪崩勾引你,不知道你心動了沒有,如果不想你的項目在線上環(huán)境面臨同樣的問題,趕緊為項目搭線起來,不經(jīng)意的舉動往往能讓你升職加薪!
在分布式系統(tǒng)中,由于網(wǎng)絡原因或自身的原因。服務一般無法保證 100% 可用,如果一個服務出現(xiàn)了問題,調(diào)用這個服務就會出現(xiàn)線程阻塞的情況,此時若有大量的請求涌入,就會出現(xiàn)多條線程阻塞等待,進而導致服務癱瘓。而由于服務與服務之間的依賴性,故障會進行傳播,相互影響之下,會對整個微服務系統(tǒng)造成災難性的嚴重后果,這就是服務故障的 “雪崩效應”!
最開始的時候,服務A~C 三個服務其樂融融的相互調(diào)用,響應也很快,為主人工作也很賣力

好景不長,主人火了,并發(fā)量上來了。可能因為服務C還不夠健壯的原因,服務C在某一天宕機了,但是服務B還是依賴服務C,不停的進行服務調(diào)用

這個時候可能大家都還沒意識到問題的嚴重性,只是懷疑可能請求太多了,導致服務器變卡了。請求繼續(xù)發(fā)送,服務A這個時候也未知問題,一邊覺得奇怪服務B是不是偷懶了,怎么還不把響應返回給它,一邊繼續(xù)發(fā)送請求。但是這個時候請求都在服務B堆積著,終于有一天服務B也累出問題了

這個時候人們開始抱怨服務A了,卻不知道服務A底層原來還依賴服務B和服務C,而這時服務B和服務C都掛了。服務A這時才想通為什么服務B之前那么久沒返回響應,原來服務B也依賴服務C啊!但是這個時候已經(jīng)晚了,請求不斷接收,不斷堆積,下場都是驚人的相似,也走向了宕機的結果。不過有一點不同的是,服務A宕機后需要承載了用戶各種的罵聲~

可悲的故事警惕了我們,微服務架構之間并沒有那么可靠。有時候真的是說掛就掛,原因各種各樣,不合理的容量設計,高并發(fā)情況下某個方法響應變慢,亦或是某臺機器的資源耗盡。我們?nèi)绻徊扇〈胧荒茏源龜溃粩嗟脑谥貑⒎掌髦醒h(huán)。但是我們可能無法杜絕雪崩的源頭,但是如果我們在問題發(fā)生后做好容錯的準備,保證下一個服務發(fā)生問題,不會影響到其他服務的正常運行,各個服務自掃家門雪,做到獨立,雪落而不雪崩!
二、容錯方案
想要防止雪崩的擴散,就要做好服務的容錯,容錯說白了就是保護自己不被其他隊友坑,帶進送人頭的行列!那我們有哪些容錯的思路呢?
1)隔離方案
它是指將系統(tǒng)按照一定的原則劃分為若干個服務模塊,各個模塊之間相互獨立,無強依賴。當有故障發(fā)生時,能將問題和影響隔離在某個模塊內(nèi)部,而不擴散風險,不涉及其他模塊,不影響整體的系統(tǒng)服務。常見的隔離方式有:線程隔離 和信號量隔離:

2)超時方案
在上游服務調(diào)用下游服務的時候,設置一個最大響應時間,如果超過這個時間下游服務還沒響應,那么就斷開連接,釋放掉線程

3)限流方案
限流就是限制系統(tǒng)的輸入和輸出流量已達到保護系統(tǒng)的目的。為了保證系統(tǒng)的穩(wěn)固運行,一旦達到需要限制的閾值,就需要限制流量并采用少量措施完成限制流量的目的

限流策略有很多,后期也會考慮出一篇專門將如何進行限流
4)熔斷方案
在互聯(lián)網(wǎng)系統(tǒng)中,當下游服務因訪問壓力過大而相應變慢或失敗的時候,上游服務為了保護系統(tǒng)整體的可用性,可以暫時切斷對下游服務的調(diào)用。這種犧牲局部,保全整體的措施就叫做熔斷

其中熔斷有分為三種狀態(tài):
- 熔斷關閉狀態(tài)(Closed)
服務沒有故障時,熔斷器所處的狀態(tài),對調(diào)用方的調(diào)用不做任何限制
- 熔斷開啟狀態(tài)(Open)
后續(xù)對該服務接口的調(diào)用不再經(jīng)過網(wǎng)絡,直接執(zhí)行本地的 fallback 方法
- 半熔斷狀態(tài)(Half-Open)
嘗試恢復服務調(diào)用,允許有限的流量調(diào)用該服務,并監(jiān)控成功率。如果成功率達到預期,則說明服務已經(jīng)恢復,進入熔斷關閉狀態(tài);如果成功率依然很低,則重新進入熔斷關閉狀態(tài)
5)降級方案
降級其實就是為服務提供一個 B計劃,一旦服務無法正常,就啟用 B計劃

方案其實有很多,但是很難說明那種方案是最好的。在開發(fā)者的世界中,沒有最好,只有最適合。那如果自己寫一個容錯方案往往是比較容易出錯的(功力高深者除外),那么為了解決這個問題,我們不妨用第三方已經(jīng)為我實現(xiàn)好的組件!
三、容錯組件
1)Hystrix
Hystrix 是 Netflix 開源的一個延遲和容錯庫,用于隔離訪問遠程系統(tǒng),服務或者第三方庫,防止級聯(lián)失敗,從而提升系統(tǒng)的可用性和容錯性
2)Resilience4J
Resilience4J是一款非常輕量,簡單,并且文檔非常清晰,豐富的熔斷工具,這是 Hystrix 官方推薦的替代品。它支持 SpringBoot 1.x/2.x 版本,而且監(jiān)控也支持和 prometheus 等多款主流產(chǎn)品進行整合
3)Sentinel
Sentinel 是阿里開源的一款斷路器的實現(xiàn),在阿里巴巴內(nèi)部也已經(jīng)大規(guī)模采用,可以說是非常穩(wěn)定
不同之處
容錯組件其實有很多,但各有風騷,下面分別說明這這三種組件的不同之處,如何抉擇,仔細斟酌!
| 功能 | Sentinel | Hystrix | resilience4j |
|---|---|---|---|
| 隔離策略 | 信號量隔離(并發(fā)線程數(shù)限流) | 線程池隔離/信號量隔離 | 信號量隔離 |
| 熔斷降級策略 | 基于響應時間,異常比率,異常數(shù) | 基于異常比率 | 基于異常比率,響應時間 |
| 實時統(tǒng)計實現(xiàn) | 時間滑動窗口(LeapArray) | 時間滑動窗口(基于Rxjava) | Ring Bit Buffer |
| 動態(tài)規(guī)則配置 | 支持多種數(shù)據(jù)源 | 支持多種數(shù)據(jù)源 | 有限支持 |
| 擴展性 | 多個擴展點 | 插件的形式 | 接口的形式 |
| 基于注解的支持 | 支持 | 支持 | 支持 |
| 限流 | 基于 QPS,支持基于調(diào)用關系的限流 | 有限的支持 | Rate LImiter |
| 流量整形 | 支持預熱模式,勻速器模式,預熱排隊模式 | 不支持 | 簡單的 Rate Limiter模式 |
| 系統(tǒng)自適應保護 | 支持 | 不支持 | 不支持 |
| 控制臺 | 提供即用的控制臺,可配置規(guī)則,查看秒級監(jiān)控,機器發(fā)現(xiàn)等 | 簡單的監(jiān)控查看 | 不提供控制臺,可對接其他監(jiān)控系統(tǒng) |
之前有說過,一個新秀想讓大伙接受并廣泛使用,肯定得具備良好的特性才能沖出老牌的包圍圈。那么 Sentinel 作為一個微服務中的新秀,只有具備讓人滿意的功能,才能被大伙接受。因此,這篇的主角便是 Sentinel ,不妨深入了解一番!
四、認識Sentinel
學會用一個組件之前,我們先需要知道這個組件是什么。
1)什么是Sentinel
Sentinel(分布式系統(tǒng)的流量防衛(wèi)兵)是阿里開源的一套用于 服務容錯 的綜合性解決方案。它以流量為切入點,從 流量控制、熔斷降級、系統(tǒng)負載保護等多個維度來保護服務的穩(wěn)定性。
2)特性
- 豐富的應用場景:Sentinel 承接了阿里巴巴近10年的雙十一大促的流量的核心場景。在秒殺、消息削峰填谷,集群流量控制、實時熔斷下游不可用應用等場景游刃有余
- 完備的實時監(jiān)控: Sentinel 提供了實時的監(jiān)控功能。通過控制臺可以看到接入應用的單臺機器的數(shù)據(jù),甚至500臺以下規(guī)模的集群的匯總情況
- 廣泛的開源生態(tài): Sentinel 提供開箱即用的與其他開源框架整合模塊,只需要引入相關的依賴進行簡單的配置即可快速接入
- 完善的 SPI 擴展點: Sentinel 提供簡單易用、完善的 SPI 擴展接口。可以通過擴展接口來快速定制邏輯。例如定制規(guī)則管理,適配動態(tài)數(shù)據(jù)源等
3)組成部分
- 核心庫(Java客戶端):不依賴任何框架/庫,能夠運行于所有的Java運行環(huán)境,同時對 Dubbo和SpringCloud 有很好的支持
- 控制臺(Dashboard):基于SpringBoot開發(fā), 打包后可以直接運行,不需要額外的 Tomcat 等應用容器
五、上手 Sentinel
既然 Sentinel 有兩個組成部分,我們分別介紹
1) 核心庫使用
最關鍵的一步便是引入依賴
<dependency>
????<groupId>com.alibaba.cloud</groupId>
????<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
然后編寫一個測試控制器
@RestController
@RequestMapping("order")
public?class?OrderController?{
????private?final?static?Logger?LOGGER?=?LoggerFactory.getLogger(OrderController.class);
????@GetMapping("/create/{id:.+}")
????public?String?createOrder(@PathVariable?String?id)?{
????????LOGGER.info("準備下單ID為?[{}]?的商品",?id);
????????LOGGER.info("成功下單了ID為?[{}]?的商品",?id);
????????return?"success";
????}
????@GetMapping("/{id:.+}")
????public?String?detail(@PathVariable?String?id)?{
????????return?StringUtils.join("獲取到了ID為",?id,?"的商品");
????}
}
2)控制臺使用
Sentinel 具備完善的控制臺, 其實就抓住了國人開發(fā)的命點。很多人看到有控制臺的使用,毫不猶豫的選擇了它!
首先我們需要下載控制臺的 Jar 包啟動運行,下載地址
下載結束進入到下載目錄中通過以下命令啟動,然后訪問
localhost:8080,即可看到頁面
java?-Dserver.port=8080?-Dcsp.sentinel.dashboard.server=localhost:8080?-Dproject.name=sentinel-dashboard?-jar?sentinel-dashboard-1.8.1.jar

- 登錄控制臺(sentinel/sentinel)

到這里我們就成功進入 Sentinel的控制臺頁面了,是不是上手十分簡單。但這里控制臺還是空蕩蕩的,那是因為我們項目還沒進行控制臺的相關配置。我們回到 store-order ?訂單服務中,在 application.yaml 中進行配置:

sertinel.transport.port為隨意端口號,用來跟控制臺交流的端口;sentinel.transport.dashboard ?為控制臺的訪問地址
配置完成后,我們便可以啟動 store-order 服務,此時再看控制臺可以發(fā)現(xiàn)已經(jīng)有了 store-order 這個服務了

可能有些小伙伴會覺得奇怪,上面說到用來跟控制臺交流的端口是干嘛用的?有問題,便有進步!這里我們可以順帶了解一下控制臺的使用原理

當 Sentinel應用啟動后,我們需要將我們的微服務程序注冊到控制臺上,也就是在配置文件中指定控制臺的地址,這個是肯定的。但是所謂用來跟控制臺交流的端口,也就是我們每個服務都會通過這個端口跟控制臺傳遞數(shù)據(jù),控制臺也可以通過此端口調(diào)用微服務中的監(jiān)控程序來獲取微服務的各種信息。因此這個端口必不可少,而且每個服務都需要具備獨立的端口號。
3)基本概念
- 資源
所謂的資源就是 Sentinel 要保護的東西。資源是 Sentinel 的關鍵概念,它可以使 Java 應用程序中的任何內(nèi)容,可以是一個服務,也可以是一個方法,甚至是一段代碼。
- 規(guī)則
所謂的規(guī)則就是用來定義如何進行保護資源的,它是作用于資源之上的,定義以什么樣的方式保護資源,主要包括了流量控制規(guī)則,熔斷降級規(guī)則以及系統(tǒng)保護規(guī)則
我們來看個簡單的例子,我們設置 order/{id}這個API 的 QPS 為1

當我們不斷刷新頁面就會發(fā)現(xiàn),已經(jīng)進行了流控

在這里面 order/{id}指的就是 資源,我們設置的QPS閾值就是 規(guī)則
4)重要功能
學會用 Sentinel 之前,我們需要清楚 Sentinel 能為我們干點什么
(1)流量控制
流量控制在網(wǎng)絡傳輸中是一個常用的概念,它用于調(diào)整網(wǎng)絡包的數(shù)據(jù)。任意時間到來的請求往往是隨機不可控的,而系統(tǒng)的處理能力是有限的。我們需要根據(jù)系統(tǒng)的處理能力對流量進行控制,Sentinel 作為一個調(diào)配器,可以根據(jù)需要把隨機的請求調(diào)整成合適的形狀。
(2)熔斷降級
當檢測到調(diào)用鏈路中某個資源出現(xiàn)不穩(wěn)定的表現(xiàn),例如請求響應時間長或者異常比例升高的時候,則對這個資源的調(diào)用進行限制,讓請求快速失敗,避免影響到其他資源而導致級聯(lián)故障
Sentinel 采用了兩種手段進行解決
- 通過并發(fā)線程數(shù)進行限制
Sentinel 通過限制資源并發(fā)線程的數(shù)量,來減少不穩(wěn)定資源對其他資源的影響。當某個資源出現(xiàn)不穩(wěn)定的情況時,例如響應時間變長,對資源的直接影響就是會造成線程數(shù)的逐步堆積。當 線程數(shù)在特定資源上堆積到一定的數(shù)量之后,對該資源的新請求就會拒絕。堆積的線程完成任務后才會開始繼續(xù)接受請求
- 通過響應時間對資源進行降級
除了對并發(fā)線程數(shù)進行控制之外,Sentinel 還可以通過響應時間來快速降級不穩(wěn)定的資源。當依賴的資源出現(xiàn)響應時間過長后,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之后才會重新恢復
這里提一嘴和 Hystrix 的區(qū)別
兩者的原則實際上都是一致的。都是當一個資源出現(xiàn)問題時,讓其快速失敗,不會波及到其他資源服務。但是在限制的實現(xiàn)上是不一樣的
- Hystrix 采用的是線程池隔離方式,優(yōu)點是做到了資源之間的隔離,缺點是增加了線程上下文切換的成本
- Sentinel 采用的是通過并發(fā)線程的數(shù)量和響應時間來對資源做限制的
個人認為 Sentinel 處理限制的方式更好一些
(3)系統(tǒng)負載保護
Sentinel 同時提供系統(tǒng)維度的自適應保護能力。當系統(tǒng)負載較高的時候,如果還持續(xù)讓請求進入可能會導致系統(tǒng)崩潰,無法響應。在集群環(huán)境下,會把本應這臺機器承載的流量轉發(fā)到其他機器上去。如果這個時候其他的機器也處在一個崩潰的邊緣狀態(tài),Sentinel 提供了對應的保護機制,讓系統(tǒng)的入口流量和負載達到一個平衡,保證系統(tǒng)在能力方位之內(nèi)處理最多的請求。
5)流控規(guī)則
流控規(guī)則,在我們上面說明 Sentinel 的基本概念時簡單演示了一下。流量控制就是用來監(jiān)控應用流量的 QPS(每秒查詢率)或并發(fā)線程數(shù)等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性。
簡單配置
簇點鏈路 ---> 選擇對應資源 ---> 添加流控

資源名:唯一名稱,默認就是請求路徑,支持自定義
針對來源: 指定對哪個微服務進行限流,默認為 default(不區(qū)分來源,全部限制)
閾值類型/單機閾值:
- QPS (每秒請求數(shù)):當調(diào)用該接口的QPS達到閾值的時候進行限流
- 線程數(shù):當調(diào)用該接口的線程數(shù)達到閾值的時候進行限流
是否集群: 這里暫時不演示集群
高級配置
我們點開 高級選項 可以看到多出了兩個額外功能

其中 流控模式 分為 三種
直接(默認):接口達到限流條件時,開啟限流
關聯(lián):當關聯(lián)的資源達到限流條件時,開啟限流(適合做應用讓步)
鏈路: 當從某個接口過來的資源達到限流條件時,開啟限流
1、關聯(lián)流控
直接流控 的方式我們在上面已經(jīng)演示過了,我們這里直接說 關聯(lián)流控 如何使用

使用方式也很簡單,只要添加相關聯(lián)的資源即可。只要關聯(lián)資源 /order/create/{id}的 QPS 每秒超過 1。那么 /order/{id} 就會觸發(fā)流控。這就是 你沖動,我買單
設置完后,我們需要請我們的老幫手 Jmeter 幫忙測試一下:

這個時候 /order/create/{id} 的QPS已經(jīng)遠遠超過1了,然后我們再試著訪問 /order/{id},發(fā)現(xiàn)已經(jīng)被限流了!

2、鏈路流控
鏈路流控模式指的是:當從某個接口過來的資源達到限流條件的時候,開啟限流。它的功能有點類似于針對來源配置項,區(qū)別在于:針對來源是針對上級微服務,而鏈路流控是針對上級接口,也就是說它的粒度更細
該模式使用麻煩一些,我們需要改造下代碼:
OrderService

OrderController

application.yaml

然后自定義 Sentinel 上下文過濾類 FilterContextConfig:

接下來我們在 Sentinel 控制臺流控中添加配置:

然后我們看測試結果,發(fā)現(xiàn)以/order/_datail02為入口訪問,會進行流控,而/order/_datail01訪問便不會進行流控

因此我們清楚了鏈路模式的入口資源是針對方法接口的
6)降級規(guī)則
降級規(guī)則指的就是當滿足什么條件的時候,對服務進行降級。Sentinel 提供了三個衡量條件:
- 慢調(diào)用比例
當資源的平均相應時間超過閾值(單位 ms)之后,資源進入準降級的狀態(tài)。如果接下來1s內(nèi)持續(xù)進入5個請求,它們的RT都持續(xù)超過這個閾值,那么在接下來的時間窗口(單位 s)之內(nèi),就會對這個方法進行降級。

- 異常比例
當資源的每秒異常總數(shù)/占通過量的比率超過閾值之后,資源就會進入降低狀態(tài),即在接下的時間窗口(單位 s)之內(nèi),對這個方法的調(diào)用都會自動的返回。異常比率的賦值范圍為 [0.0, 1.0]

- 異常數(shù)
當資源近1分鐘的異常數(shù)目超過閾值之后就會直接進行降級。但是這里需要注意的是,由于統(tǒng)計時間窗口是分鐘級別的,若時間窗口小于60s,則結束熔斷狀態(tài)后仍可能再進入熔斷狀態(tài)

7)熱點規(guī)則
熱點參數(shù)流控規(guī)則是一種更加細粒度的流控規(guī)則,它允許將規(guī)則具體到參數(shù)上。這里我們可以在代碼里面具體看下怎么使用
@SentinelResource("order")??//?不添加該注解標識,?熱點規(guī)則不生效
@GetMapping("/_datail03")
public?String?detail03(String?arg1,?String?arg2)?{
????return?StringUtils.join(arg1,?arg2);
}
該API接收兩個參數(shù)arg1和arg2,這個時候我們對這個資源添加參數(shù)流控

弄完上面配置后,我們就可以在瀏覽器進行測試了
當參數(shù)為第二個的時候,無論一秒刷新幾次都不會觸發(fā)流控

當參數(shù)為第一個的時候,只要QPS超過了1,就會觸發(fā)流控

這個配置也有高級選項,可以更細顆粒的對參數(shù)進行限制,這里就不再演示了。

8)系統(tǒng)規(guī)則
系統(tǒng)保護規(guī)則是從應用級別的入口流量進行控制,從單臺機器總體的 Load、RT、線程數(shù)、入口QPS、CPU 使用率五個維度監(jiān)控應用數(shù)據(jù),讓系統(tǒng)盡可能跑在最大吞吐量的同時保證系統(tǒng)整體的穩(wěn)定性
image-20210613220336221- Load: 僅對 Linux/Unix 有效。當系統(tǒng)的 load 超過閾值時,且系統(tǒng)當前的并發(fā)線程數(shù)超過系統(tǒng)容量時才會觸發(fā)系統(tǒng)保護。系統(tǒng)容量是由系統(tǒng)的
maxQPS * minRT計算而出,設定的參考值可以參考 CPU 核數(shù) * 2.5 - RT: 當單臺機器上所有入口流量的平均 RT 達到閾值就會觸發(fā)系統(tǒng)保護,單位是毫秒
- 線程數(shù): 當單臺機器上所有入口流量的并發(fā)線程數(shù)達到閾值是就會觸發(fā)保護
- 入口QPS: 當單臺機器上所有入口流量的QPS達到閾值就會觸發(fā)系統(tǒng)保護
- CPU使用率: 當單臺機器上所有入口流量的CPU使用率達到閾值就會觸發(fā)系統(tǒng)保護
9)授權規(guī)則
在某些場景下,我們需要根據(jù)調(diào)用來源來判斷該次請求是否允許放行,這個時候我們可以使用Sentinel的來源訪問控制的功能。來源訪問控制根據(jù)資源的請求來源判斷資源是否能夠通過。

- 白名單: 只有請求來源位于白名單內(nèi)才能通過
- 黑名單: 請求來源位于黑名單時不予通過,其余的則放行通過
那么問題來了,流控應用是啥玩意?要用這個流控應用,我們還需要借助 Sentinel 中的 RequestOriginParser 接口來處理來源。只要 Sentinel 保護的接口資源被訪問,Sentinel 就會調(diào)用 RequestOriginParser 的實現(xiàn)類去解析訪問源
CustomRequestOriginParser
public?class?CustomRequestOriginParser?implements?RequestOriginParser?{
????@Override
????public?String?parseOrigin(HttpServletRequest?httpServletRequest)?{
????????return?httpServletRequest.getParameter("api");
????}
}
然后我們添加授權規(guī)則

該規(guī)則的作用是,只有當請求URL中帶有參數(shù) api=detail03 才能訪問成功,否則失敗。以下便是測試結果


六、擴展 Sentinel
1)@SentinelResource
這個注解我們上面已經(jīng)用過,不知道小伙伴們有沒有注意到,上面避開沒講就是為了在這詳細的介紹下!
該注解的作用就是用來定義資源點。當我們定義了資源點之后,就可以通過 Sentinel 控制臺來設置限流和降級策略來對資源點進行保護。同時還可以通過該注解來指定出現(xiàn)異常時候的處理策略。
我們點進注解可以看到該注解中存在許多屬性

| 屬性 | 作用 |
|---|---|
| value | 資源點名稱 |
| blockHandle | 處理BlockException的函數(shù)名稱,函數(shù)要求: 1. 必須是 public2. 返回類型參數(shù)與原方法要一致 3. 默認需和原方法在同一個類中。如果希望使用其他類的函數(shù),可以配置 blockHandlerClass,并制定blockHandlerClass 里面的方法 |
| blackHandlerClass | 存放 blockHandler 的類,對應的處理函數(shù)必須用 static ?修飾 |
| fallback | 用于在拋出異常時候提供 fallback 處理邏輯。fallback 函數(shù)可以針對所有類型的異常(除了exceptionsToIgnore 中排除的異常),函數(shù)要求: 1. 返回類型與原方法一致 2. 參數(shù)類型需和原方法匹配 3. 默認需和原方法在同一個類中。若希望使用其他類的函數(shù),可配置 fallbackClass,并指定對應的方法 |
| fallbackClass | 存放 fallback 的類,對應的處理函數(shù)必須用 static ? 修飾 |
| defaultFallback | 用于通用的 fallback 邏輯。默認fallback函數(shù)可以針對所有類型的異常進行處理。若同時配置了 fallback 和 defaultFallback,以fallback為準。函數(shù)要求: 1. 返回類型與原方法一致 2. 方法參數(shù)列表為空,或者有一個 Throwable 類型的參數(shù)。 3. 默認需要和原方法在同一個類中。若希望使用其他類的函數(shù),可配置 fallbackClass ,并指定 fallbackClass 里面的方法。 |
| exceptionsToIgnore | 指定排除掉哪些異常。排除的異常不會計入異常統(tǒng)計,也不會進入fallback邏輯,而是原樣拋出。 |
| exceptionsToTrace | 需要trace的異常 |
我們這里簡單使用演示一下
- 將限流和降級方法定義在原方法同一個類中

- 限流和降級方法定義不在原方法同一個類中

然后我們做個簡單的流控設置:

訪問結果:


這種提示的方式顯然更加友好!
2)Sentinel 規(guī)則持久化
已經(jīng)上手嘗試的同學可能會發(fā)現(xiàn)一個問題,當我們的項目重啟,或者 Sentinel 控制臺重啟都會導致配置被清空了!這是因為這些規(guī)則默認是存放在內(nèi)存中,這可是很大的問題!因此規(guī)則持久化是一個必不可少的工作!當然在 Sentinel 也已經(jīng)很好的支持了這項功能,處理邏輯如下:

實話說配置類代碼有點長,這里直接貼代碼了,有需要的小伙伴可以拷過去直接用!
public?class?FilePersistence?implements?InitFunc?{
????@Override
????public?void?init()?throws?Exception?{
????????String?ruleDir?=?new?File("").getCanonicalPath()?+?"/sentinel-rules";
????????String?flowRulePath?=?ruleDir?+?"/flow-rule.json";
????????String?degradeRulePath?=?ruleDir?+?"/degrade-rule.json";
????????String?systemRulePath?=?ruleDir?+?"/system-rule.json";
????????String?authorityRulePath?=?ruleDir?+?"/authority-rule.json";
????????String?paramFlowRulePath?=?ruleDir?+?"/param-flow-rule.json";
????????this.mkdirIfNotExits(ruleDir);
????????this.createFileIfNotExits(flowRulePath);
????????this.createFileIfNotExits(degradeRulePath);
????????this.createFileIfNotExits(systemRulePath);
????????this.createFileIfNotExits(authorityRulePath);
????????this.createFileIfNotExits(paramFlowRulePath);
????????//?流控規(guī)則sentinel
????????ReadableDataSource<String,?List<FlowRule>>?flowRuleRDS?=?new
????????????????FileRefreshableDataSource<>(
????????????????flowRulePath,
????????????????flowRuleListParser
????????);
????????FlowRuleManager.register2Property(flowRuleRDS.getProperty());
????????WritableDataSource<List<FlowRule>>?flowRuleWDS?=?new
????????????????FileWritableDataSource<>(
????????????????flowRulePath,
????????????????this::encodeJson
????????);
????????WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
????????//?降級規(guī)則
????????ReadableDataSource<String,?List<DegradeRule>>?degradeRuleRDS?=?new
????????????????FileRefreshableDataSource<>(
????????????????degradeRulePath,
????????????????degradeRuleListParser
????????);
????????DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
????????WritableDataSource<List<DegradeRule>>?degradeRuleWDS?=?new
????????????????FileWritableDataSource<>(
????????????????degradeRulePath,
????????????????this::encodeJson
????????);
????????WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
????????//?系統(tǒng)規(guī)則
????????ReadableDataSource<String,?List<SystemRule>>?systemRuleRDS?=?new
????????????????FileRefreshableDataSource<>(
????????????????systemRulePath,
????????????????systemRuleListParser
????????);
????????SystemRuleManager.register2Property(systemRuleRDS.getProperty());
????????WritableDataSource<List<SystemRule>>?systemRuleWDS?=?new
????????????????FileWritableDataSource<>(
????????????????systemRulePath,
????????????????this::encodeJson
????????);
????????WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
????????//?授權規(guī)則
????????ReadableDataSource<String,?List<AuthorityRule>>?authorityRuleRDS?=?new
????????????????FileRefreshableDataSource<>(
????????????????authorityRulePath,
????????????????authorityRuleListParser
????????);
????????AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
????????WritableDataSource<List<AuthorityRule>>?authorityRuleWDS?=?new
????????????????FileWritableDataSource<>(
????????????????authorityRulePath,
????????????????this::encodeJson
????????);
????????WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
//?熱點參數(shù)規(guī)則
????????ReadableDataSource<String,?List<ParamFlowRule>>?paramFlowRuleRDS?=?new
????????????????FileRefreshableDataSource<>(
????????????????paramFlowRulePath,
????????????????paramFlowRuleListParser
????????);
????????ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
????????WritableDataSource<List<ParamFlowRule>>?paramFlowRuleWDS?=?new
????????????????FileWritableDataSource<>(
????????????????paramFlowRulePath,
????????????????this::encodeJson
????????);
????????ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
????}
????private?final?Converter<String,?List<FlowRule>>?flowRuleListParser?=?source?->
????????????JSON.parseObject(
????????????????????source,
????????????????????new?TypeReference<List<FlowRule>>()?{
????????????????????}
????????????);
????private?final?Converter<String,?List<DegradeRule>>?degradeRuleListParser?=?source
????????????->?JSON.parseObject(
????????????source,
????????????new?TypeReference<List<DegradeRule>>()?{
????????????}
????);
????private?final?Converter<String,?List<SystemRule>>?systemRuleListParser?=?source?->
????????????JSON.parseObject(
????????????????????source,
????????????????????new?TypeReference<List<SystemRule>>()?{
????????????????????}
????????????);
????private?final?Converter<String,?List<AuthorityRule>>?authorityRuleListParser?=
????????????source?->?JSON.parseObject(
????????????????????source,
????????????????????new?TypeReference<List<AuthorityRule>>()?{
????????????????????}
????????????);
????private?final?Converter<String,?List<ParamFlowRule>>?paramFlowRuleListParser?=
????????????source?->?JSON.parseObject(
????????????????????source,
????????????????????new?TypeReference<List<ParamFlowRule>>()?{
????????????????????}
????????????);
????private?void?mkdirIfNotExits(String?filePath)?throws?IOException?{
????????File?file?=?new?File(filePath);
????????if?(!file.exists())?{
????????????file.mkdirs();
????????}
????}
????private?void?createFileIfNotExits(String?filePath)?throws?IOException?{
????????File?file?=?new?File(filePath);
????????if?(!file.exists())?{
????????????file.createNewFile();
????????}
????}
????private?<T>?String?encodeJson(T?t)?{
????????return?JSON.toJSONString(t);
????}
}
然后在 resources 下創(chuàng)建配置目錄 META-INF/services ,然后添加文件com.alibaba.csp.sentinel.init.InitFunc在文件中添加配置類的全路徑


這樣子我們啟動項目的時候就會生成 Sentinel 的配置文件了

當我們在控制臺中添加一條流控規(guī)則后,對應的 json 文件就會有對應的配置


到這里我們就完成了 Sentinel 的持久化功能,到這里我們也完成了對 SpringCloud 中Sentinel 的介紹!
后面會繼續(xù)整理關于 SpringCloud 組件的文章,敬請關注!
不要空談,不要貪懶,和小菜一起做個吹著牛X做架構的程序猿吧~點個關注做個伴,讓小菜不再孤單。咱們下文見!
今天的你多努力一點,明天的你就能少說一句求人的話!
我是小菜,一個和你一起變強的男人。
??微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!
