輕松掌握!Spring Boot 集成 Resilience4j 實(shí)現(xiàn)斷路器的全流程實(shí)戰(zhàn)
在現(xiàn)代分布式系統(tǒng)架構(gòu)中,服務(wù)之間的通信非常頻繁,尤其是微服務(wù)架構(gòu)下,每個(gè)微服務(wù)都會(huì)依賴其他服務(wù)的響應(yīng)。雖然這種架構(gòu)能夠有效地提高系統(tǒng)的擴(kuò)展性和靈活性,但也帶來了一些問題,比如網(wǎng)絡(luò)延遲、依賴的服務(wù)不可用、超時(shí)等。為了避免整個(gè)系統(tǒng)因?yàn)槟硞€(gè)服務(wù)不可用而崩潰,我們可以使用 斷路器模式 來防止這種“雪崩效應(yīng)”的發(fā)生。
斷路器模式(Circuit Breaker Pattern)作為一種保護(hù)機(jī)制,可以幫助我們監(jiān)控和控制外部服務(wù)的調(diào)用。在服務(wù)出現(xiàn)故障時(shí),斷路器可以快速響應(yīng)并阻止后續(xù)調(diào)用,從而避免不必要的等待和資源消耗。本文將結(jié)合代碼示例,講解如何在 Spring Boot 項(xiàng)目中使用 Resilience4j 實(shí)現(xiàn)斷路器,并展示如何在前后端代碼中進(jìn)行交互,前端部分使用 Thymeleaf 模板引擎,結(jié)合 jQuery 和 Bootstrap 實(shí)現(xiàn)。
斷路器模式簡(jiǎn)介
斷路器模式 是應(yīng)對(duì)外部服務(wù)故障的一種保護(hù)機(jī)制。它的核心思想是,當(dāng)某個(gè)外部服務(wù)調(diào)用頻繁失敗時(shí),不再繼續(xù)嘗試調(diào)用該服務(wù),而是直接返回一個(gè)預(yù)設(shè)的結(jié)果或執(zhí)行一個(gè)備用邏輯(即回退方法)。斷路器模式通常包含以下三種狀態(tài):
- 關(guān)閉狀態(tài) (Closed):當(dāng)服務(wù)正常工作時(shí),斷路器處于關(guān)閉狀態(tài),所有請(qǐng)求都會(huì)直接通過并調(diào)用目標(biāo)服務(wù)。
- 打開狀態(tài) (Open):當(dāng)檢測(cè)到服務(wù)連續(xù)多次失敗,斷路器會(huì)進(jìn)入打開狀態(tài),此時(shí)所有請(qǐng)求都會(huì)被快速失敗,直接觸發(fā)回退方法。
- 半開狀態(tài) (Half-Open):經(jīng)過一段時(shí)間后,斷路器會(huì)自動(dòng)嘗試允許少量請(qǐng)求通過,如果這些請(qǐng)求成功,斷路器會(huì)回到關(guān)閉狀態(tài);否則,繼續(xù)保持打開狀態(tài)。
這種機(jī)制能夠有效防止系統(tǒng)因?yàn)槟硞€(gè)服務(wù)的不可用而產(chǎn)生的資源浪費(fèi)和響應(yīng)延遲。
運(yùn)行效果:
圖片
若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時(shí)遇到問題需要咨詢交流,歡迎加入下方的知識(shí)星球。
引入依賴 (pom.xml)
首先,我們需要在 pom.xml 文件中引入相關(guān)的依賴。這里包括 Spring Boot、Resilience4j、Lombok 以及用于模板渲染的 Thymeleaf 依賴。
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>circuit-breaker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>circuit-breaker</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Resilience4j 斷路器 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 (application.yml)
接下來,我們需要在 application.yml 中為 Resilience4j 斷路器配置相關(guān)參數(shù)。這些參數(shù)用于定義斷路器的行為,包括滑動(dòng)窗口的大小、失敗率閾值、斷路器打開狀態(tài)的等待時(shí)間等。
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10 # 增大滑動(dòng)窗口以計(jì)算多個(gè)請(qǐng)求的失敗率
failureRateThreshold: 50 # 設(shè)置更高的失敗率閾值,例如 50%
waitDurationInOpenState: 10000 # 打開狀態(tài)持續(xù)時(shí)間 10 秒
permittedNumberOfCallsInHalfOpenState: 3 # 半開狀態(tài)下允許通過的請(qǐng)求數(shù)量
minimumNumberOfCalls: 5 # 最少需要 5 個(gè)請(qǐng)求才能計(jì)算失敗率
automaticTransitionFromOpenToHalfOpenEnabled: true
instances:
myService:
baseConfig: default
timeout:
default:
timeoutDuration: 2s # 設(shè)置超時(shí)時(shí)間為 2 秒
timeoutDuration:請(qǐng)求超過 2 秒沒有返回時(shí)會(huì)觸發(fā)超時(shí)異常。
failureRateThreshold:將失敗率設(shè)置為 50%,這樣只要一半的請(qǐng)求失敗,斷路器就會(huì)打開。
minimumNumberOfCalls:設(shè)置為 5,以確保在少量請(qǐng)求中也能計(jì)算失敗率
讀取配置類 (@ConfigurationProperties)
我們可以使用 @ConfigurationProperties 注解來讀取配置文件中的斷路器相關(guān)配置,并通過 Lombok 自動(dòng)生成類的 getter 和 setter 方法。
package com.icoderoad.circuit.breaker.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
@ConfigurationProperties(prefix = "resilience4j.circuitbreaker")
public class CircuitBreakerProperties {
private CircuitBreakerConfig configs;
private CircuitBreakerInstance instances;
}
package com.icoderoad.circuit.breaker.config;
import lombok.Data;
@Data
class CircuitBreakerConfig {
private DefaultConfig defaultConfig;
}
package com.icoderoad.circuit.breaker.config;
import lombok.Data;
@Data
class DefaultConfig {
private int slidingWindowSize;
private int failureRateThreshold;
private int waitDurationInOpenState;
}
package com.icoderoad.circuit.breaker.config;
import lombok.Data;
@Data
class CircuitBreakerInstance {
private MyServiceConfig myService;
}
package com.icoderoad.circuit.breaker.config;
import lombok.Data;
@Data
class MyServiceConfig {
private String baseConfig;
}
實(shí)體類
假設(shè)我們有一個(gè)簡(jiǎn)單的 User 實(shí)體類,Lombok 可以幫助我們簡(jiǎn)化代碼:
package com.icoderoad.circuit.breaker.entity;
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private String email;
}
配置類
在你的配置類或主應(yīng)用類中,添加一個(gè)方法,使用 @Bean 注解來定義 RestTemplate。
package com.icoderoad.circuit.breaker.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
斷路器業(yè)務(wù)邏輯實(shí)現(xiàn)
接下來,在服務(wù)層中,我們通過 RestTemplate 調(diào)用外部服務(wù),并為該方法應(yīng)用斷路器。為了模擬外部調(diào)用,我們將把外部服務(wù)調(diào)用更改為服務(wù)內(nèi)部的調(diào)用(例如 /api/internalService),來模擬服務(wù)依賴。
package com.icoderoad.circuit.breaker.service;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "myService", fallbackMethod = "fallback")
public String callInternalService() {
// 模擬服務(wù)內(nèi)調(diào)用
return restTemplate.getForObject("http://localhost:8080/api/internalService", String.class);
}
// 回退方法,當(dāng)斷路器觸發(fā)時(shí)執(zhí)行
public String fallback(Throwable t) {
return "內(nèi)部服務(wù)不可用,請(qǐng)稍后再試。";
}
}
控制器
我們創(chuàng)建一個(gè)控制器來處理前端發(fā)來的請(qǐng)求,并調(diào)用服務(wù)層的 callInternalService 方法。
package com.icoderoad.circuit.breaker.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.icoderoad.circuit.breaker.service.MyService;
@RestController
public class CircuitBreakerController {
private final MyService myService;
public CircuitBreakerController(MyService myService) {
this.myService = myService;
}
@GetMapping("/api/call")
public String callService() {
return myService.callInternalService();
}
}
另外,為了模擬外部調(diào)用服務(wù)的內(nèi)部服務(wù)接口,我們可以簡(jiǎn)單創(chuàng)建一個(gè)模擬的內(nèi)部服務(wù)端點(diǎn)。
package com.icoderoad.circuit.breaker.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class InternalServiceController {
@GetMapping("/api/internalService")
public String internalService() {
try {
// 模擬服務(wù)延遲5秒,超過Resilience4j設(shè)置的2秒超時(shí)
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
return "服務(wù)調(diào)用失敗!";
}
// 模擬隨機(jī)失敗
if (Math.random() > 0.5) {
throw new RuntimeException("模擬服務(wù)異常");
}
return "服務(wù)調(diào)用成功!";
}
}
在這個(gè)例子中,我們使用 RestTemplate 發(fā)起對(duì)本地服務(wù)的調(diào)用,模擬服務(wù)依賴。當(dāng)請(qǐng)求失敗時(shí),
斷路器會(huì)進(jìn)入打開狀態(tài),隨后的請(qǐng)求將直接調(diào)用 fallback 方法,返回一個(gè)預(yù)定義的消息以避免等待。
前端實(shí)現(xiàn) (Thymeleaf + jQuery + Bootstrap)
前端部分將使用 Thymeleaf 作為模板引擎,結(jié)合 jQuery 和 Bootstrap 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的界面,用戶可以通過點(diǎn)擊按鈕來觸發(fā)服務(wù)調(diào)用,并顯示結(jié)果。
在 src/main/resources/templates 目錄下創(chuàng)建 index.html 文件:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Resilience4j 斷路器示例</title>
<link rel="stylesheet" >
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
</head>
<body>
<div class="container">
<h1>斷路器示例</h1>
<button id="callService" class="btn btn-primary">調(diào)用服務(wù)</button>
<div id="response" class="mt-3"></div>
</div>
<script>
$('#callService').click(function () {
$.ajax({
url: '/api/call',
type: 'GET',
success: function (data) {
$('#response').text(data);
},
error: function () {
$('#response').text('服務(wù)調(diào)用失??!');
}
});
});
</script>
</body>
</html>
在這個(gè)前端頁面中,當(dāng)用戶點(diǎn)擊按鈕時(shí),將通過 jQuery 發(fā)起一個(gè) AJAX 請(qǐng)求,并顯示服務(wù)的響應(yīng)結(jié)果。
斷路器的運(yùn)行機(jī)制及測(cè)試
啟動(dòng)應(yīng)用后,訪問頁面 http://localhost:8080并點(diǎn)擊“調(diào)用服務(wù)”按鈕,系統(tǒng)會(huì)嘗試調(diào)用 /api/internalService。在正常情況下,頁面會(huì)顯示“服務(wù)調(diào)用成功!”的響應(yīng)。但如果在短時(shí)間內(nèi)多次觸發(fā)失?。梢允謩?dòng)引入錯(cuò)誤或拋出異常),斷路器會(huì)打開,此時(shí)調(diào)用會(huì)返回回退方法的結(jié)果 “內(nèi)部服務(wù)不可用,請(qǐng)稍后再試?!?/p>
通過觀察,可以看到斷路器的幾種狀態(tài)變化:
- 在正常工作時(shí),服務(wù)調(diào)用正常。
- 當(dāng)連續(xù)失敗達(dá)到閾值時(shí),斷路器打開,直接返回回退方法的結(jié)果。
- 一段時(shí)間后,斷路器進(jìn)入半開狀態(tài),允許部分請(qǐng)求通過,如果恢復(fù)正常則關(guān)閉斷路器。
結(jié)論
斷路器模式 是微服務(wù)架構(gòu)中確保系統(tǒng)健壯性的重要模式之一。它能夠避免由于某個(gè)依賴服務(wù)的故障導(dǎo)致系統(tǒng)的整體崩潰。通過 Resilience4j,我們可以方便地在 Spring Boot 應(yīng)用中集成斷路器功能,并通過配置靈活地調(diào)整其行為。
本文詳細(xì)講解了如何通過 Spring Boot 與 Resilience4j 實(shí)現(xiàn)斷路器模式,并結(jié)合 Thymeleaf 前端模板與 jQuery 的異步請(qǐng)求展示了一個(gè)完整的前后端交互流程。在實(shí)際項(xiàng)目中,可以進(jìn)一步擴(kuò)展 Resilience4j 的功能,比如結(jié)合 限流、重試 等模式,以提高系統(tǒng)的可用性和穩(wěn)定性。通過這種機(jī)制,不僅能夠提高系統(tǒng)對(duì)不可預(yù)見故障的處理能力,還能為用戶提供更好的體驗(yàn),減少因?yàn)榉?wù)不可用帶來的負(fù)面影響。