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

Cbuc丶發表於2021-06-27

大家好,我是小菜。
一個希望能夠成為 吹著牛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做架構的程式猿吧~點個關注做個伴,讓小菜不再孤單。我們們下文見!

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

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

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

相關文章