使用Resilience4j實施反應式斷路器 - Wenqi
本文將重點介紹使用 Spring Cloud 斷路器庫 Resilience4j 實現反應式斷路器。
為什麼選擇 Resilience4j?
我們可以使用兩個主要庫來實現斷路器。Netflix Hystrix,它採用物件導向的設計,其中對外部系統的呼叫必須包含在HystrixCommand提供的多種功能中。但是,在 SpringOne 2019 中,Spring 宣佈 Hystrix Dashboard 將從 Spring Cloud 3.1 版本中刪除,這使其正式棄用。使用已棄用的庫不是一個好主意。所以選擇很明確,就是 Resilience4j!
Resilience4j 是一個受 Hystrix 啟發的獨立庫,但建立在函數語言程式設計的原則之上。Resilience4J 提供了高階函式(裝飾器),以通過斷路器、速率限制器或隔板來增強任何功能介面、lambda 表示式或方法引用。
Resilience4J 的其他優點包括更精細的配置選項(例如關閉斷路器模式所需的成功執行次數)和更輕的依賴項佔用空間。
我們將使用兩個 Spring Boot 微服務來演示如何實現響應式斷路器:
- 客戶服務,充當 REST API 提供者,提供客戶 CRUD 端點。
- customer-service-client,它WebClient通過 Spring Boot Starter Webflux 庫來呼叫 REST API。
步驟 1. 新增 POM 依賴項
由於我們選擇WebClient使用 REST API,因此我們需要將 Spring Cloud Circuit Breaker Reactor Resilience4J 依賴項新增到我們的 REST 客戶端應用程式中。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency> |
第2步。新增斷路器配置bean
CircuitBreakerConfig類帶有一組預設的斷路器配置值,如果我們選擇對所有的斷路器使用預設的配置值,我們可以建立一個Customize bean,它被傳遞給ReactiveResilience4JCircuitBreakerFactory。該工廠的configureDefault方法可用於提供預設配置。示例片段如下。
@Bean public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4)).build()).build()); } |
如果我們選擇使用自定義的配置值,我們將需要如下定義我們的bean("customer-service "只是一個REST客戶端例項的樣本,你可以使用你給你的REST客戶端應用程式的任何例項名稱)。
@Bean public Customizer<ReactiveResilience4JCircuitBreakerFactory> customerServiceCusomtizer() { return factory -> { factory.configure(builder -> builder .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(2)).build()) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()), "customer-service"); }; } |
第3步. 為斷路器屬性新增配置
如果我們定義了我們的自定義配置豆,我們還需要在application.yml中新增斷路器的配置,例如(僅是示例值,數字應根據應用的使用場景進行調整)。
resilience4j.circuitbreaker: instances: customer-service: failureRateThreshold: 50 minimumNumberOfCalls: 10 slidingWindowType: TIME_BASED slidingWindowSize: 10 waitDurationInOpenState: 50s permittedNumberOfCallsInHalfOpenState: 3 |
- failureRateThreshold。當故障率等於或大於閾值時,斷路器過渡到開放狀態並開始短路呼叫。在我們的例子中,這個值是50%,這意味著如果2個請求中有1個失敗,就會達到閾值,這將使斷路器進入開放狀態。
- MinimumNumberOfCalls: 這個屬性確保一旦執行了最低數量的呼叫,就能計算出故障率。在我們的例子中,在開始計算故障率之前必須執行10個請求。
- slidingWindowType(滑動視窗型別)。配置滑動視窗的型別,用於記錄斷路器關閉時的呼叫結果。滑動視窗可以是基於計數的,也可以是基於時間的。
- slidingWindowSize(滑動視窗尺寸)。配置滑動視窗的大小,用於記錄斷路器關閉時的呼叫結果。
- waitDurationInOpenState: 斷路器在從開放狀態過渡到半開放狀態之前應該等待的時間。在我們的例子中,它是50秒。
- permittedNumberOfCallsInHalfOpenState: 配置斷路器處於半開狀態時允許的呼叫數量。在我們的例子中,限制是3,這意味著在10秒的視窗中只處理3個請求。
第4步。實施Circuit Breaker
現在所有的配置都到位了,我們可以開始使用Circuit Breaker從客戶端裝飾我們的REST API呼叫。在下面的例子中,我們通過建構函式注入WebClient和ReactiveCircuitBreakerFactory到CustomerCientController。然後,我們使用webClient來觸發對傳遞進來的CustomerVO和/或customerId的CRUD呼叫。注意 "轉換 "部分,我們在ReactiveCircuitBreakerFactory的幫助下為 "customer-service "建立ReactiveCircuitBreaker例項(Rcb為ReactiveCircuitBreaker型別)。執行斷路器的行是rcb.run(...)。在下面的示例控制器中,當異常被丟擲時,我們為POST/GET/PUT呼叫返回一個空白的CustomerVO物件作為後退響應。對於DELETE呼叫,我們將返回傳入的customerId作為回退。因此,在REST API供應商停機的情況下,我們不會得到500內部伺服器錯誤,而是通過正確的實施Circuit Breaker,收到後備響應。
@RestController @Slf4j @RequiredArgsConstructor public class CustomerClientController { private final WebClient webClient; private final ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory; @PostMapping("/customers") public Mono<CustomerVO> createCustomer(CustomerVO customerVO){ return webClient.post() .uri("/customers") //.header("Authorization", "Bearer MY_SECRET_TOKEN") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .body(Mono.just(customerVO), CustomerVO.class) .retrieve() .bodyToMono(CustomerVO.class) .timeout(Duration.ofMillis(10_000)) .transform(it -> { ReactiveCircuitBreaker rcb = reactiveCircuitBreakerFactory.create("customer-service"); return rcb.run(it, throwable -> Mono.just(CustomerVO.builder().build())); }); } @GetMapping("/customers/{customerId}") public Mono<CustomerVO> getCustomer(@PathVariable String customerId) { return webClient .get().uri("/customers/" + customerId) .retrieve() .bodyToMono(CustomerVO.class) .transform(it -> { ReactiveCircuitBreaker rcb = reactiveCircuitBreakerFactory.create("customer-service"); return rcb.run(it, throwable -> Mono.just(CustomerVO.builder().build())); }); } @PutMapping("/customers/{customerId}") public Mono<CustomerVO> updateCustomer(@PathVariable String customerId, CustomerVO customerVO){ return webClient.put() .uri("/customers/" + customerVO.getCustomerId()) //.header("Authorization", "Bearer MY_SECRET_TOKEN") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .body(Mono.just(customerVO), CustomerVO.class) .retrieve() .bodyToMono(CustomerVO.class) .transform(it -> { ReactiveCircuitBreaker rcb = reactiveCircuitBreakerFactory.create("customer-service"); return rcb.run(it, throwable -> Mono.just(CustomerVO.builder().build())); }); } @DeleteMapping("/customers/{customerId}") public Mono<String> deleteCustomer(@PathVariable String customerId){ return webClient.delete() .uri("/customers/" + customerId) .retrieve() .bodyToMono(String.class) .transform(it -> { ReactiveCircuitBreaker rcb = reactiveCircuitBreakerFactory.create("customer-service"); return rcb.run(it, throwable -> Mono.just(customerId)); }); } } |
問題1:Swagger使用者介面在Webflux中不工作
由於我們引入了Webflux庫來使用WebClient,你可能會注意到你的swagger UI最初並不工作。為了讓它工作,請確保以下步驟得到執行。
在pom中新增以下依賴項。
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>${springfox.version}</version> </dependency> |
如果你已經實現了註釋@EnableSwagger2WebFlux,請刪除該註釋。
現在訪問swagger的URL應該是:http://<YOUR_APP_SERVER>:<YOUR_APP_PORT>/swagger-ui/,一定要加上結尾"/"。例如,http://localhost:8100/swagger-ui/。
如何為沒有返回型別的端點實現斷路?
對於在其響應體中沒有返回內容的端點,如REST API提供商中的以下端點,在REST客戶端,如果我們將呼叫該端點的相應方法標記為返回Mono<Void>,ReactiveCircuitBreaker將無法工作。在REST API提供者關閉的情況下,你會看到500伺服器錯誤,這完全違背了擁有Circuit Breaker的目的。
@DeleteMapping(value = "/{customerId}") public ResponseEntity deleteCustomer(@PathVariable String customerId) throws Exception { customerService.deleteCustomer(customerId); return ResponseEntity.noContent().build(); } |
在非反應式斷路器的實現中,對於沒有返回型別的方法,我們可以使用 "CheckedRunnable",做如下工作(示例)。
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("customer-service"); CheckedRunnable runnable = () -> customerClient.deleteCustomer(customerId); Try.run(circuitBreaker.decorateCheckedRunnable(runnable)).get(); |
但是,在反應式斷路器中,ReactiveCircuitBreaker沒有這樣的介面來裝飾CheckedRunnable,那麼我們該怎麼做?經過一些調查和實驗,我注意到我們可以操縱這種端點的返回型別,使其返回一個一般的型別,如String。簡單地說,如果一個端點,如DELETE呼叫在伺服器端返回Void,我們仍然可以在客戶端操縱該DELETE呼叫的返回型別,以返回一個簡單的型別,如String,只是傳回輸入到該端點的String。例如,在客戶端我們可以這樣實現DELETE呼叫。
public Mono<String> deleteCustomer(@PathVariable String customerId){ return webClient.delete() .uri("/customers/" + customerId) .retrieve() .bodyToMono(String.class) .transform(it -> { ReactiveCircuitBreaker rcb = reactiveCircuitBreakerFactory.create("customer-service"); return rcb.run(it, throwable -> Mono.just(customerId)); }); } |
注意我們正在返回Mono<String>而不是Mono<Void>,而且我們在.bodyToMono(String.class)行指定了返回型別為String,這就是為什麼我們可以簡單地呼叫ReactiveCircuitBreaker的run方法來呼叫裝飾過的反應式斷路器函式。這是我能想到的唯一方法,可以解決ReactiveCircuitBreaker沒有decorateCheckedRunnable方法來處理沒有返回型別的方法。
完整原始碼:GitHub repository
相關文章
- 使用Resilience4J實現斷路器模式模式
- Java 專案中使用 Resilience4j 框架實現隔斷機制/斷路器Java框架
- resilience4j不夠用?自制分散式斷路器來幫忙 -Nicolas分散式
- 帶有Resilience4j斷路器的Spring雲閘道器 - romeSpring
- 資訊系統應用實施斷想
- 使用Spring實現反應式事務(Reactive Transactions)SpringReact
- 《反應式應用開發》之“什麼是反應式應用”
- 使用Spring Boot + Resilience 4j實現斷路器Spring Boot
- 智慧斷路器應用方案之智慧路燈杆應用方案
- Spring Cloud:使用Hystrix實現斷路器原理詳解(下)SpringCloud
- 使用 Resilience4j 框架實現重試機制框架
- 實施網路安全-A
- 斷路器HystrixCircuitBreakerUI
- 《反應式宣言》——TheReactiveManifestoReact
- 谷歌指責歐盟反壟斷立場大轉變稱無實施罰款依據谷歌
- Spring Boot中使用斷路器模式實現彈性微服務Spring Boot模式微服務
- 智慧斷路器與傳統斷路器的區別?
- Java反應式事件溯源之第 4 部分:控制器Java事件
- Hystrix斷路器在微服務閘道器中的應用微服務
- 實施網路安全-C
- JavaScript函式的反應性JavaScript函式
- 制定和實施網路安全事件響應計劃(1)事件
- 智慧斷路器應用方案之智慧消防用電
- 熔斷器 Hystrix 原始碼解析 —— 斷路器 HystrixCircuitBreaker原始碼UI
- 美軍切斷最大軍事基地電源以此來測試網路攻擊下的真實反應
- 微服務斷路器模式實現:Istio vs Hystrix微服務模式
- 什麼是反應式應用開發?
- 作為一名實施,需要自己反編譯、網路抓包、猜測資料庫,乾的全不像實施的活……編譯資料庫
- 智慧斷路器應用方案之產業園智慧用電產業
- 智慧斷路器應用方案之銀行智慧用電
- Java反應式事件溯源:領域Java事件
- 07.CircuitBreaker斷路器UI
- Java 專案中使用 Resilience4j 框架實現故障隔離Java框架
- 使用Spring Boot反應式R2DBC實現PostgreSQL的CRUD操作原始碼 - RajeshSpring BootSQL原始碼
- 網際網路反壟斷成關注焦點 網際網路往前發展反壟斷不存在“既往不咎”
- 智慧斷路器應用方案之智慧農業噴灌方案
- 智慧斷路器應用方案之工廠智慧用電方案
- resilience4j