使用Spring Boot + Resilience 4j實現斷路器
Resilience 4j提供以下功能。
- 斷路器
- RateLimiter
- 艙壁
- 重試
- 快取記憶體
- TimeLimiter
如果您打算在Spring Boot中使用它,可以使用Starter。請注意,Spring Boot 1.x和2.x系列之間的artifactId似乎有所不同。另外,上面只包含CircuitBreaker和RateLimiter,在使用其他功能時需要單獨新增依賴項。(由於未準備好AutoConfigure,您還需要自己定義bean。)
這次我將總結如何在Spring Boot 2.x系列中使用CircuitBreaker和RateLimiter。
環境
- JDK 8
- Spring Boot 2.1.2.RELEASE
- Resilience 4j 0.13.2
斷路器
當某些具有微服務的服務發生故障時,可以臨時阻止對故障服務的訪問並防止故障傳播。
CircuitBreaker有三種狀態:Closed,Open,HalfOpen。如果是正常的,則它是關閉的,如果處理失敗超過一定數量,它將變為開啟並且訪問被阻止。當在開啟狀態下經過一段時間後,進入HalfOpen狀態。如果處理在HalfOpen狀態下失敗超過一定量,則返回到關閉狀態。
在Resilience 4j中,處理的成功和失敗由環形緩衝器Ring Bit Buffer管理,並且當緩衝器中的故障數超過設定的速率時,狀態轉變。
斷路器使用狀態中的Ring Bit Buffer CLOSED來儲存呼叫的成功或失敗狀態。成功的呼叫儲存為0位,失敗的呼叫儲存為1位。Ring Bit Buffer具有(可配置的)固定大小。環位緩衝區在內部使用類似資料結構的 BitSet 來儲存與布林陣列相比節省記憶體的位。BitSet使用long []陣列來儲存這些位。這意味著BitSet只需要一個包含16個長(64位)值的陣列來儲存1024個呼叫的狀態。
例如,如果環形緩衝區的大小為10,則必須至少評估10個呼叫,然後才能計算故障率。如果僅評估了9個呼叫,即使所有9個呼叫都失敗,斷路器也不會開啟。
用於Closed - > Open和HalfOpen - > Closed判斷的環形緩衝區是不同的,可以定義大小,但使用相同的判斷條件(錯誤率)。
在持續關閉時間結束後,斷路器狀態從OPEN更改為HALF_OPEN並允許呼叫以檢視後端是否仍然不可用或已再次可用。
斷路器使用另一個(可配置的)環位緩衝區來評估HALF_OPEN狀態中的故障率。如果故障率高於配置的閾值,則狀態將更改回OPEN。如果故障率低於或等於閾值,則狀態變回CLOSED。
此外,處理的成功和失敗由異常判斷。預設情況下,如果任何異常丟擲異常,則會將其視為處理失敗,但您也可以指定要將其視為失敗的條件。
設定
application.yml你可以設定定義多個斷路器。
resilience4j: circuitbreaker: backends: circuitA: # #斷路器名 truering-buffer-size-in-closed-state: 5 #環形緩衝區是在封閉狀態下使用的大小 ring-buffer-size-in-half-open-state: 3 # HalfOpen 狀態下的大小 wait-duration-in-open-state : 5000 # Open持續時間 failure-rate-threshold: 50 # 到開啟狀態的閾值 record-failure-predicate: com.example.resilience.RecordFailurePredicate ignore-exceptions: #沒有失敗#異常類和計數 - com.example.resilience.exception.BusinessException record-exceptions: #異常類失敗和計數 - com.example.resilience.exception.SystemException circuitB: ・・・
如果你想只考慮一個特定的異常和故障使用RecordExceptions,當你不想忽視特定的異常時使用ignoreExceptions。
有兩種方法可以使用Spring AOP並在函式中實現它。無論哪種實現,如果Circuit處於Open狀態,它將生成CircuitBreakerOpenException。
在以下實現示例中,為簡單起見,它不是微服務。最初RestTemplate,我認為這將是Service Class 呼叫其他服務API等使用等的過程。
Spring AOP實現
通過@CircuitBreaker(name = "hogehoge")註釋到類或方法上則可以啟用斷路器。如果在類指定這個註釋,則為所有公共方法啟用斷路器。
@Service @CircuitBreaker(name = <font>"circuitB"</font><font>) <b>public</b> <b>class</b> CircuitBreakerService { <b>public</b> String aop(String str) { <b>if</b> (str == <b>null</b>) { <b>throw</b> <b>new</b> RuntimeException(); } <b>return</b> </font><font>"success!!"</font><font>; } } </font>
呼叫者不用考慮任何事情,只需執行該方法即可。
@RestController @RequestMapping(<font>"/circuit"</font><font>) <b>public</b> <b>class</b> CircuitBreakerController { <b>private</b> <b>final</b> CircuitBreakerService service; <b>public</b> CircuitBreakerController(CircuitBreakerService service) { <b>this</b>.service = service; } @GetMapping(</font><font>"/aop"</font><font>) <b>public</b> String aop(@RequestParam(required = false) String str) { <b>return</b> service.aop(str); } } </font>
如何寫業務函式?
@Service <b>public</b> <b>class</b> CircuitBreakerService { <b>public</b> String func(String str) { <b>if</b> (str == <b>null</b>) { <b>throw</b> <b>new</b> RuntimeException(); } <b>return</b> <font>"success!!"</font><font>; } } </font>
呼叫端使用斷路器的decorate~方法修飾要呼叫的方法。
@RestController @RequestMapping(<font>"/circuit"</font><font>) <b>public</b> <b>class</b> CircuitBreakerController { <b>private</b> <b>final</b> CircuitBreaker circuitBreaker; <b>private</b> <b>final</b> CircuitBreakerService service; <b>public</b> CircuitBreakerController(CircuitBreakerRegistry registry, CircuitBreakerService service) { <b>this</b>.circuitBreaker = registry.circuitBreaker(</font><font>"circuitA"</font><font>); <b>this</b>.service = service; } @GetMapping(</font><font>"/func"</font><font>) <b>public</b> String func(@RequestParam(required = false) String str) { <b>return</b> CircuitBreaker.decorateSupplier(circuitBreaker, () -> service.func(str)).get(); } } </font>
後備處理
接下來,如果發生故障,執行回退過程怎麼辦?在Hystrix 的情況下,通過指定@HystrixCommand("hogeMethod"),由於Resilience4j沒有設定的這樣的功能,必須自己實現。
@RestController @RequestMapping(<font>"/circuit"</font><font>) <b>public</b> <b>class</b> CircuitBreakerController { <b>private</b> <b>final</b> CircuitBreaker circuitBreaker; <b>private</b> <b>final</b> CircuitBreakerService service; <b>public</b> CircuitBreakerController(CircuitBreakerRegistry registry, CircuitBreakerService service) { <b>this</b>.circuitBreaker = registry.circuitBreaker(</font><font>"circuitA"</font><font>); <b>this</b>.service = service; } @GetMapping(</font><font>"/func"</font><font>) <b>public</b> String func(@RequestParam(required = false) String str) { <b>return</b> Try.ofSupplier(CircuitBreaker.decorateSupplier(circuitBreaker, () -> service.func(str))) .recover(CircuitBreakerOpenException.<b>class</b>, </font><font>"Circuit is Open!!"</font><font>) .recover(RuntimeException.<b>class</b>, </font><font>"fallback!!"</font><font>).get(); } } </font>
完整的原始碼位於下方。 https://github.com/d-yosh/spring-boot-resilience4j-example
RateLimiter
您可以限制每單位時間的執行次數。
單位時間是一個週期,並且可以在一個週期中執行的數量是有限的。如果它超過了可以在一個迴圈中執行的上限,則讓它等待,如果等待時間超過超時時間,則發生RequestNotPermitted。
在application.yml可以定義多個RateLimiter。
resilience4j:
ratelimiter:
limiters:
limiterA: # #RateLimiter名稱
limit-for-period: 1 # 每時間單位#可執行處理數
limit-refresh-period-in-millis: 10000 # #單位時間(毫秒)
timeout-in-millis: 10000 #timeout time(milliseconds)
limiterB:
・・・
它與斷路器實現方式相同,有兩種方法可以使用Spring AOP並在業務函式中編寫它。實現方法也類似於斷路器。
@Service @RateLimiter(name = <font>"limiterB"</font><font>) <b>public</b> <b>class</b> RateLimiterService { <b>public</b> String func() { <b>return</b> LocalDateTime.now().toString(); } } </font>
呼叫者不用考慮任何事情,只需執行該方法即可。
@RestController @RequestMapping(<font>"/ratelimiter"</font><font>) <b>public</b> <b>class</b> RateLimiterController { <b>private</b> <b>final</b> RateLimiterService service; <b>public</b> RateLimiterController(RateLimiterService service) { <b>this</b>.service = service; } @GetMapping(</font><font>"aop"</font><font>) <b>public</b> String aop() { <b>return</b> service.aop(); } } </font>
函式方法:
@Service <b>public</b> <b>class</b> RateLimiterService { <b>public</b> String func() { <b>return</b> LocalDateTime.now().toString(); } } @RestController @RequestMapping(<font>"/ratelimiter"</font><font>) <b>public</b> <b>class</b> RateLimiterController { <b>private</b> <b>final</b> RateLimiter rateLimiter; <b>private</b> <b>final</b> RateLimiterService service; <b>public</b> RateLimiterController(RateLimiterRegistry registry, RateLimiterService service) { <b>this</b>.rateLimiter = registry.rateLimiter(</font><font>"limiterA"</font><font>); <b>this</b>.service = service; } @GetMapping(</font><font>"func"</font><font>) <b>public</b> String func() { <b>return</b> Try.ofSupplier(RateLimiter.decorateSupplier(rateLimiter, service::func)) .recover(RequestNotPermitted.<b>class</b>, </font><font>"Request Not Permitted!!"</font><font>).get(); } } </font>
後備處理,與斷路器一樣,沒有自動執行回退處理的機制,因此您需要自己實現它。
完整的原始碼位於下方。 https://github.com/d-yosh/spring-boot-resilience4j-example
單位時間為5秒,超時時間為1秒,每單位時間的執行次數為1。如果同時傳送多個請求,則會發出失敗請求。(如果您同時請求三個,則至少一個將始終失敗。)
$ curl http:<font><i>//localhost:8080/ratelimiter/func</i></font><font> 2019-01-22T23:09:35.612 $ curl http:</font><font><i>//localhost:8080/ratelimiter/func</i></font><font> Request Not Permitted!! </font>