《吃透微服務》 – 服務容錯之Sentinel

大家好,我是小菜。
一個希望能夠成為 吹着牛X談架構 的男人!如果你也想成為我想成為的人,不然點個關注做個伴,讓小菜不再孤單!

本文主要介紹 SpringCloud中Sentinel

如有需要,可以參考

如有幫助,不忘 點贊

微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!

上篇我們已經了解到微服務中重要的組件之一 — 服務網關Gateway 。我們在取精排糠的同時,不可否認微服務給我們帶來的好處。其中承載高並發的好處更是讓各大公司趨之若鶩!

《吃透微服務》 – 服務網關之Gateway

但是想要接收高並發,自然要接收它帶來的一系列問題。在微服務架構中,我們將業務拆分成了一個個服務,這樣的好處之一便是分擔壓力,一個服務頂不住了,下一個服務頂上去。但是服務與服務之間可以相互調用,由於網絡原因或自身的原因,服務並不能保證百分百可用,也就是各大公司現在追尋的幾個9(99.99%,99.999%)可用!如果單個服務出現問題,調用這個服務就會出現網絡延遲,此時如果正好有大量的網絡湧入,勢必會形成任務堆積,導致服務癱瘓!

空口無憑,小菜給你整個示例:

OrderController

這裡我們通過接口模擬了下單的場景,其中通過線程休眠的方式模擬了網絡延遲的場景。接下來我們還需要改一下 tomcat 中並發的線程數

applicatio.yaml

server:
  tomcat:
    max-threads: 10  # 默認的並發數為200

當這一切準備就緒好,我們這個時候還需要壓測工具 Jmeter 的幫助(不會操作的同學具體看以下使用)

  1. 首先打開Jmeter軟件,選擇新建線程組

  1. 設置請求線程數

  1. 設置HTTP請求取樣器

  1. 設置請求的接口

完成上面步驟就可以點擊開始了。不過測試之前我們確保兩個API都是可以訪問的:

image-20210612220817883

然後開始壓力測試,當大量請求發送到創建訂單的接口時,我們這時候通過網頁訪問 detail API 發現請求一直在阻塞,過一會才聯通!

這無疑是一個開發炸彈,而這便是高並髮帶來的問題。看到這裡,恭喜你成功見證了一場服務雪崩的問題。那不妨帶着這份興趣繼續往下看,會給你驚喜的。

Sentinel

一、服務雪崩

我們開頭直接用服務雪崩勾引你,不知道你心動了沒有,如果不想你的項目在線上環境面臨同樣的問題,趕緊為項目搭線起來,不經意的舉動往往能讓你升職加薪!

在分佈式系統中,由於網絡原因或自身的原因。服務一般無法保證 100% 可用,如果一個服務出現了問題,調用這個服務就會出現線程阻塞的情況,此時若有大量的請求湧入,就會出現多條線程阻塞等待,進而導致服務癱瘓。而由於服務與服務之間的依賴性,故障會進行傳播,相互影響之下,會對整個微服務系統造成災難性的嚴重後果,這就是服務故障的 「雪崩效應」

最開始的時候,服務A~C 三個服務其樂融融的相互調用,響應也很快,為主人工作也很賣力

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

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

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

可悲的故事警惕了我們,微服務架構之間並沒有那麼可靠。有時候真的是說掛就掛,原因各種各樣,不合理的容量設計,高並發情況下某個方法響應變慢,亦或是某台機器的資源耗盡。我們如果不採取措施,只能坐以待斃,不斷的在重啟服務器中循環。但是我們可能無法杜絕雪崩的源頭,但是如果我們在問題發生後做好容錯的準備,保證下一個服務發生問題,不會影響到其他服務的正常運行,各個服務自掃家門雪,做到獨立,雪落而不雪崩

二、容錯方案

想要防止雪崩的擴散,就要做好服務的容錯,容錯說白了就是保護自己不被其他隊友坑,帶進送人頭的行列!那我們有哪些容錯的思路呢?

1)隔離方案

它是指將系統按照一定的原則劃分為若干個服務模塊,各個模塊之間相互獨立,無強依賴。當有故障發生時,能將問題和影響隔離在某個模塊內部,而不擴散風險,不涉及其他模塊,不影響整體的系統服務。常見的隔離方式有:線程隔離 和信號量隔離:

2)超時方案

在上游服務調用下游服務的時候,設置一個最大響應時間,如果超過這個時間下游服務還沒響應,那麼就斷開連接,釋放掉線程

3)限流方案

限流就是限制系統的輸入和輸出流量已達到保護系統的目的。為了保證系統的穩固運行,一旦達到需要限制的閾值,就需要限制流量並採用少量措施完成限制流量的目的

限流策略有很多,後期也會考慮出一篇專門將如何進行限流

4)熔斷方案

在互聯網系統中,當下游服務因訪問壓力過大而相應變慢或失敗的時候,上游服務為了保護系統整體的可用性,可以暫時切斷對下游服務的調用。這種犧牲局部,保全整體的措施就叫做熔斷

其中熔斷有分為三種狀態:

  • 熔斷關閉狀態(Closed)

服務沒有故障時,熔斷器所處的狀態,對調用方的調用不做任何限制

  • 熔斷開啟狀態(Open)

後續對該服務接口的調用不再經過網絡,直接執行本地的 fallback 方法

  • 半熔斷狀態(Half-Open)

嘗試恢復服務調用,允許有限的流量調用該服務,並監控成功率。如果成功率達到預期,則說明服務已經恢復,進入熔斷關閉狀態;如果成功率依然很低,則重新進入熔斷關閉狀態

5)降級方案

降級其實就是為服務提供一個 B計劃,一旦服務無法正常,就啟用 B計劃

方案其實有很多,但是很難說明那種方案是最好的。在開發者的世界中,沒有最好,只有最適合。那如果自己寫一個容錯方案往往是比較容易出錯的(功力高深者除外),那麼為了解決這個問題,我們不妨用第三方已經為我實現好的組件!

三、容錯組件

1)Hystrix

Hystrix 是 Netflix 開源的一個延遲和容錯庫,用於隔離訪問遠程系統,服務或者第三方庫,防止級聯失敗,從而提升系統的可用性和容錯性

2)Resilience4J

Resilience4J是一款非常輕量,簡單,並且文檔非常清晰,豐富的熔斷工具,這是 Hystrix 官方推薦的替代品。它支持 SpringBoot 1.x/2.x 版本,而且監控也支持和 prometheus 等多款主流產品進行整合

3)Sentinel

Sentinel 是阿里開源的一款斷路器的實現,在阿里巴巴內部也已經大規模採用,可以說是非常穩定

不同之處

容錯組件其實有很多,但各有風騷,下面分別說明這這三種組件的不同之處,如何抉擇,仔細斟酌!

功能 Sentinel Hystrix resilience4j
隔離策略 信號量隔離(並發線程數限流) 線程池隔離/信號量隔離 信號量隔離
熔斷降級策略 基於響應時間,異常比率,異常數 基於異常比率 基於異常比率,響應時間
實時統計實現 時間滑動窗口(LeapArray) 時間滑動窗口(基於Rxjava) Ring Bit Buffer
動態規則配置 支持多種數據源 支持多種數據源 有限支持
擴展性 多個擴展點 插件的形式 接口的形式
基於註解的支持 支持 支持 支持
限流 基於 QPS,支持基於調用關係的限流 有限的支持 Rate LImiter
流量整形 支持預熱模式,勻速器模式,預熱排隊模式 不支持 簡單的 Rate Limiter模式
系統自適應保護 支持 不支持 不支持
控制台 提供即用的控制台,可配置規則,查看秒級監控,機器發現等 簡單的監控查看 不提供控制台,可對接其他監控系統

之前有說過,一個新秀想讓大夥接受並廣泛使用,肯定得具備良好的特性才能衝出老牌的包圍圈。那麼 Sentinel 作為一個微服務中的新秀,只有具備讓人滿意的功能,才能被大夥接受。因此,這篇的主角便是 Sentinel ,不妨深入了解一番!

四、認識Sentinel

學會用一個組件之前,我們先需要知道這個組件是什麼。

1)什麼是Sentinel

Sentinel(分佈式系統的流量防衛兵)是阿里開源的一套用於 服務容錯 的綜合性解決方案。它以流量為切入點,從 流量控制熔斷降級系統負載保護等多個維度來保護服務的穩定性。

2)特性

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近10年的雙十一大促的流量的核心場景。在秒殺、消息削峰填谷,集群流量控制、實時熔斷下游不可用應用等場景遊刃有餘
  • 完備的實時監控: Sentinel 提供了實時的監控功能。通過控制台可以看到接入應用的單台機器的數據,甚至500台以下規模的集群的匯總情況
  • 廣泛的開源生態: Sentinel 提供開箱即用的與其他開源框架整合模塊,只需要引入相關的依賴進行簡單的配置即可快速接入
  • 完善的 SPI 擴展點: Sentinel 提供簡單易用、完善的 SPI 擴展接口。可以通過擴展接口來快速定製邏輯。例如定製規則管理,適配動態數據源等

3)組成部分

  • 核心庫(Java客戶端):不依賴任何框架/庫,能夠運行於所有的Java運行環境,同時對 Dubbo和SpringCloud 有很好的支持
  • 控制台(Dashboard):基於SpringBoot開發, 打包後可以直接運行,不需要額外的 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 具備完善的控制台, 其實就抓住了國人開發的命點。很多人看到有控制台的使用,毫不猶豫的選擇了它!

  • 首先我們需要下載控制台的 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 服務,此時再看控制台可以發現已經有了 store-order 這個服務了

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

當 Sentinel應用啟動後,我們需要將我們的微服務程序註冊到控制台上,也就是在配置文件中指定控制台的地址,這個是肯定的。但是所謂用來跟控制台交流的端口,也就是我們每個服務都會通過這個端口跟控制台傳遞數據,控制台也可以通過此端口調用微服務中的監控程序來獲取微服務的各種信息。因此這個端口必不可少,而且每個服務都需要具備獨立的端口號

3)基本概念

  • 資源

所謂的資源就是 Sentinel 要保護的東西。資源是 Sentinel 的關鍵概念,它可以使 Java 應用程序中的任何內容,可以是一個服務,也可以是一個方法,甚至是一段代碼。

  • 規則

所謂的規則就是用來定義如何進行保護資源的,它是作用於資源之上的,定義以什麼樣的方式保護資源,主要包括了流量控制規則,熔斷降級規則以及系統保護規則


我們來看個簡單的例子,我們設置 order/{id}這個API 的 QPS 為1

當我們不斷刷新頁面就會發現,已經進行了流控

在這裏面 order/{id}指的就是 資源,我們設置的QPS閾值就是 規則

4)重要功能

學會用 Sentinel 之前,我們需要清楚 Sentinel 能為我們干點什麼

(1)流量控制

流量控制在網絡傳輸中是一個常用的概念,它用於調整網絡包的數據。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制,Sentinel 作為一個調配器,可以根據需要把隨機的請求調整成合適的形狀。

(2)熔斷降級

當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或者異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其他資源而導致級聯故障

Sentinel 採用了兩種手段進行解決

  1. 通過並發線程數進行限制

Sentinel 通過限制資源並發線程的數量,來減少不穩定資源對其他資源的影響。當某個資源出現不穩定的情況時,例如響應時間變長,對資源的直接影響就是會造成線程數的逐步堆積。當 線程數在特定資源上堆積到一定的數量之後,對該資源的新請求就會拒絕。堆積的線程完成任務後才會開始繼續接受請求

  1. 通過響應時間對資源進行降級

除了對並發線程數進行控制之外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長後,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之後才會重新恢復

這裡提一嘴和 Hystrix 的區別

兩者的原則實際上都是一致的。都是當一個資源出現問題時,讓其快速失敗,不會波及到其他資源服務。但是在限制的實現上是不一樣的

  • Hystrix 採用的是線程池隔離方式,優點是做到了資源之間的隔離,缺點是增加了線程上下文切換的成本
  • Sentinel 採用的是通過並發線程的數量和響應時間來對資源做限制的

個人認為 Sentinel 處理限制的方式更好一些

(3)系統負載保護

Sentinel 同時提供系統維度的自適應保護能力。當系統負載較高的時候,如果還持續讓請求進入可能會導致系統崩潰,無法響應。在集群環境下,會把本應這台機器承載的流量轉發到其他機器上去。如果這個時候其他的機器也處在一個崩潰的邊緣狀態,Sentinel 提供了對應的保護機制,讓系統的入口流量和負載達到一個平衡,保證系統在能力方位之內處理最多的請求。

5)流控規則

流控規則,在我們上面說明 Sentinel 的基本概念時簡單演示了一下。流量控制就是用來監控應用流量的 QPS(每秒查詢率)或並發線程數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰衝垮,從而保障應用的高可用性。

簡單配置

簇點鏈路 —> 選擇對應資源 —> 添加流控

  • 資源名:唯一名稱,默認就是請求路徑,支持自定義

  • 針對來源: 指定對哪個微服務進行限流,默認為 default(不區分來源,全部限制)

  • 閾值類型/單機閾值

    1. QPS (每秒請求數):當調用該接口的QPS達到閾值的時候進行限流
    2. 線程數:當調用該接口的線程數達到閾值的時候進行限流
  • 是否集群: 這裡暫時不演示集群

高級配置

我們點開 高級選項 可以看到多出了兩個額外功能

其中 流控模式 分為 三種

  • 直接(默認):接口達到限流條件時,開啟限流

  • 關聯:當關聯的資源達到限流條件是,開啟限流(適合做應用讓步)

  • 鏈路: 當從某個接口過來的資源達到限流條件時,開啟限流

1、關聯流控

直接流控 的方式我們在上面已經演示過了,我們這裡直接說 關聯流控 如何使用

使用方式也很簡單,只要添加相關聯的資源即可。只要關聯資源 /order/create/{id}的 QPS 每秒超過 1。那麼 /order/{id} 就會觸發流控。這就是 你衝動,我買單

設置完後,我們需要請我們的老幫手 Jmeter 幫忙測試一下:

這個時候 /order/create/{id} 的QPS已經遠遠超過1了,然後我們再試着訪問 /order/{id},發現已經被限流了!

2、鏈路流控

鏈路流控模式指的是:當從某個接口過來的資源達到限流條件的時候,開啟限流。它的功能有點類似於針對來源配置項,區別在於: 針對來源是針對上級微服務,而鏈路流控是針對上級接口,也就是說它的粒度更細

該模式使用麻煩一些,我們需要改造下代碼:

OrderService

OrderController

application.yaml

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

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

然後我們看測試結果,發現以 /order/_datail02為入口訪問,會進行流控,而/order/_datail01訪問便不會進行流控

因此我們清楚了鏈路模式的入口資源是針對方法接口的

6)降級規則

降級規則指的就是當滿足什麼條件的時候,對服務進行降級。Sentinel 提供了三個衡量條件:

  • 慢調用比例

當資源的平均相應時間超過閾值(單位 ms)之後,資源進入准降級的狀態。如果接下來1s內持續進入5個請求,它們的RT都持續超過這個閾值,那麼在接下來的時間窗口(單位 s)之內,就會對這個方法進行降級。

  • 異常比例

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

  • 異常數

當資源近1分鐘的異常數目超過閾值之後就會直接進行降級。但是這裡需要注意的是,由於統計時間窗口是分鐘級別的,若時間窗口小於60s,則結束熔斷狀態後仍可能再進入熔斷狀態

7)熱點規則

熱點參數流控規則是一種更加細粒度的流控規則,它允許將規則具體到參數上。這裡我們可以在代碼裏面具體看下怎麼使用

@SentinelResource("order")  // 不添加該註解標識, 熱點規則不生效
@GetMapping("/_datail03")
public String detail03(String arg1, String arg2) {
    return StringUtils.join(arg1, arg2);
}

該API接收兩個參數arg1arg2,這個時候我們對這個資源添加參數流控

弄完上面配置後,我們就可以在瀏覽器進行測試了

當參數為第二個的時候,無論一秒刷新幾次都不會觸發流控

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

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

8)系統規則

系統保護規則是從應用級別的入口流量進行控制,從單台機器總體的 Load、RT、線程數、入口QPS、CPU 使用率五個維度監控應用數據,讓系統儘可能跑在最大吞吐量的同時保證系統整體的穩定性

image-20210613220336221

  • Load: 僅對 Linux/Unix 有效。當系統的 load 超過閾值時,且系統當前的並發線程數超過系統容量時才會觸發系統保護。系統容量是由系統的 maxQPS * minRT 計算而出,設定的參考值可以參考 CPU 核數 * 2.5
  • RT: 當單台機器上所有入口流量的平均 RT 達到閾值就會觸發系統保護,單位是毫秒
  • 線程數: 當單台機器上所有入口流量的並發線程數達到閾值是就會觸發保護
  • 入口QPS: 當單台機器上所有入口流量的QPS達到閾值就會觸發系統保護
  • CPU使用率: 當單台機器上所有入口流量的CPU使用率達到閾值就會觸發系統保護

9)授權規則

在某些場景下,我們需要根據調用來源來判斷該次請求是否允許放行,這個時候我們可以使用Sentinel的來源訪問控制的功能。來源訪問控制根據資源的請求來源判斷資源是否能夠通過

image-20210613211659301

  • 白名單: 只有請求來源位於白名單內才能通過
  • 黑名單: 請求來源位於黑名單時不予通過,其餘的則放行通過

那麼問題來了,流控應用是啥玩意?要用這個流控應用,我們還需要藉助 Sentinel 中的 RequestOriginParser 接口來處理來源。只要 Sentinel 保護的接口資源被訪問,Sentinel 就會調用 RequestOriginParser 的實現類去解析訪問源

CustomRequestOriginParser

public class CustomRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter("api");
    }
}

然後我們添加授權規則

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

六、擴展 Sentinel

1)@SentinelResource

這個註解我們上面已經用過,不知道小夥伴們有沒有注意到,上面避開沒講就是為了在這詳細的介紹下!

該註解的作用就是用來定義資源點。當我們定義了資源點之後,就可以通過 Sentinel 控制台來設置限流和降級策略來對資源點進行保護。同時還可以通過該註解來指定出現異常時候的處理策略。

我們點進註解可以看到該註解中存在許多屬性

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

我們這裡簡單使用演示一下

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

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

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

訪問結果:

這種提示的方式顯然更加友好!

2)Sentinel 規則持久化

已經上手嘗試的同學可能會發現一個問題,當我們的項目重啟,或者 Sentinel 控制台重啟都會導致配置被清空了!這是因為這些規則默認是存放在內存中,這可是很大的問題!因此規則持久化是一個必不可少的工作!當然在 Sentinel 也已經很好的支持了這項功能,處理邏輯如下:

實話說配置類代碼有點長,這裡直接貼代碼了,有需要的小夥伴可以拷過去直接用!

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);
        // 流控規則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);
        // 降級規則
        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);
        // 系統規則
        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);
        // 授權規則
        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);
// 熱點參數規則
        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 下創建配置目錄 META-INF/services ,然後添加文件com.alibaba.csp.sentinel.init.InitFunc在文件中添加配置類的全路徑

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

當我們在控制台中添加一條流控規則後,對應的 json 文件就會有對應的配置

到這裡我們就完成了 Sentinel 的持久化功能,到這裡我們也完成了對 SpringCloud 中Sentinel 的介紹!

後面會繼續整理關於 SpringCloud 組件的文章,敬請關注!

不要空談,不要貪懶,和小菜一起做個吹着牛X做架構的程序猿吧~點個關注做個伴,讓小菜不再孤單。咱們下文見!

今天的你多努力一點,明天的你就能少說一句求人的話!

我是小菜,一個和你一起變強的男人。 💋

微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!