0 環境
- 操作系統:win10
- 編輯器:idea
- springcloud版本:H版
1 前言
Resilience4j 是受Netflix的Hysrix項目啟發,專門為Java 8 和函數式編程設計的輕量級容錯框架。
- 主要功能
- CircuitBreaker(熔斷器)
- RateLimiter(限流)
- Retry(請求重試)
- 限時
- 緩存
- 信號量的隔離
2 初見
這是個maven項目 在test中 先體驗一下斷路器 限流 請求重試
2.1 maven項目的創建
- 新建maven子模塊 ...下一步即可
2.2 導入pom.xml依賴
- 為了省事 我直接全cv過來了
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>0.13.2</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
<version>0.13.2</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>0.13.2</version>
</dependency>
2.3 創建測試類

2.4 CircuitBreaker(熔斷器)
- 正常的斷路器
// 正常斷路器
@Test
public void test(){
// 獲取一個CircuitBreakerRegistry實例 可調用ofDefaults獲取實例(可自定義屬性)
CircuitBreakerRegistry registry = CircuitBreakerRegistry.ofDefaults();
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
// 默認50 故障率閥值百分比 超過這個閥值 斷路器就會打開
.failureRateThreshold(50)
// 斷路器保持打開時間 在到達設置時間后 斷路器會進入到half open狀態
.waitDurationInOpenState(Duration.ofMillis(1000))
// 當斷路器處於half open狀態時 環形緩沖區的大小
.ringBufferSizeInOpenState(2)
.ringBufferSizeInClosedState(2)
.build();
// 2種CircuitBreaker調用方式
CircuitBreakerRegistry registry1 = CircuitBreakerRegistry.of(config);
CircuitBreaker breaker = registry1.circuitBreaker("learn");
CircuitBreaker breaker1 = registry.circuitBreaker("learn1", config);
//
CheckedFunction0<String> supplier = CircuitBreaker.decorateCheckedSupplier(breaker, () -> "hello resilience4j");
Try<String> map = Try.of(supplier)
.map(v -> v + " you are fish");
System.out.println(map.isSuccess());
System.out.println(map.get());
System.out.println("<========================================>");
CheckedFunction0<String> supplier1 = CircuitBreaker.decorateCheckedSupplier(breaker1, () -> "hello resilience4j");
Try<String> map1 = Try.of(supplier)
.map(v -> v + " you are fish");
System.err.println(map1.isSuccess());
System.err.println(map1.get());
}

- 異常斷路器
// 一個異常的斷路器
// 需要手動調用2次onError --> ringBufferSizeInClosedState(2) --> 當有2條故障數據才會統計 --> 斷路器才會開啟
@Test
public void test1(){
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
// 默認50 故障率閥值百分比 超過這個閥值 斷路器就會打開
.failureRateThreshold(50)
// 斷路器保持打開的時間 在到達設置的時間之后 斷路器會進入到half open狀態
.waitDurationInOpenState(Duration.ofMillis(1000))
// 當斷路器處於half open狀態時(環形緩沖區大小)
.ringBufferSizeInClosedState(2)
.build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker breaker1 = registry.circuitBreaker("learn");
// 獲取斷路器的狀態
System.out.println(breaker1.getState());
breaker1.onError(0, new RuntimeException());
System.out.println(breaker1.getState());
breaker1.onError(0, new IllegalArgumentException());
System.out.println(breaker1.getState());
CheckedFunction0<String> supplier = CircuitBreaker.decorateCheckedSupplier(breaker1, () -> "hello resilience4j");
Try<String> map = Try.of(supplier)
.map(v -> v + " hello kitty");
System.out.println("<========================================>");
System.out.println("是否成功:" + map.isSuccess());
System.out.println("獲取值:" + map.get());
}

2.5 限流
// 限流 和斷路器類似
@Test
public void test2(){
RateLimiterConfig build = RateLimiterConfig.custom()
// 閾值刷新的時間
.limitRefreshPeriod(Duration.ofMillis(1000))
// 限制頻次
.limitForPeriod(3)
// 冷卻時間
.timeoutDuration(Duration.ofMillis(1000))
.build();
RateLimiter limiter = RateLimiter.of("learning", build);
CheckedRunnable runnable = RateLimiter.decorateCheckedRunnable(limiter, () -> {
System.out.println(new Date());
});
// 執行4次
Try.run(runnable)
.andThenTry(runnable)
.andThenTry(runnable)
.andThenTry(runnable)
.onFailure(t -> System.out.println(t.getMessage()));
}

2.6 請求重試
// 請求重試
@Test
public void test3(){
RetryConfig config = RetryConfig.custom()
// 重試次數
.maxAttempts(5)
// 重試間隔
.waitDuration(Duration.ofMillis(500))
// 重試異常
.retryExceptions(RuntimeException.class)
.build();
Retry retry = Retry.of("leaning1", config);
Retry.decorateRunnable(retry, new Runnable() {
int count = 0;
// 重試功能開啟后 執行run方法 若拋出異常 會自動觸發重試功能
@Override
public void run() {
if (count++ < 4){
System.out.println(count);
throw new RuntimeException();
}
}
}).run();
}

2.7 小結
- 方法調用很類似
- 斷路器 --> 滿足條件 開啟斷路器
- 限流 --> 類似限制每個人領多少個口罩
- 請求重試 --> 類似n顧茅廬
3 再見之請求重試
- 請求重試 微服務版
3.1 創建springboot項目

3.2 請求重試pom.xml配置
還需要添加resilience4j相關組件 需要排除未配置的組件 不然會報錯呢
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.3.1</version>
<exclusions>
<!-- 沒有配置的 先排除 不然會報錯 -->
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
</exclusion>
</exclusions>
3.3 請求重試yml配置
// 為了避免寫錯 直接從這個RetryConfigurationProperties類里找到InstanceProperties類里的屬性cv
public static class InstanceProperties {
@Nullable
private Duration waitDuration;
@Nullable
private Integer maxRetryAttempts;
@Nullable
private Class<? extends Predicate<Throwable>> retryExceptionPredicate;
@Nullable
private Class<? extends Predicate<Object>> resultPredicate;
@Nullable
private Class<? extends Throwable>[] retryExceptions;
@Nullable
private Class<? extends Throwable>[] ignoreExceptions;
@Nullable
private Integer eventConsumerBufferSize;
@Nullable
private Boolean enableExponentialBackoff;
private Double exponentialBackoffMultiplier;
@Nullable
private Boolean enableRandomizedWait;
private Double randomizedWaitFactor;
@Nullable
private String baseConfig;
resilience4j:
retry:
retry-aspect-order: 133 # 表示Retry優先級(級別高於比如ratelimiter bulkhead timelimiter) 值越小 -> 越大
backends:
# 設置組名
retryA:
# 對比之前的案例
# 重試次數
maxRetryAttempts: 4
# 重試等待
waitDuration: 400
# 間隔乘數(場景: 正好每次間隔為1的時候卡頓 它就有用了 間隔就變了 例如 1 1.1 1.21....)
exponentialBackoffMultiplier: 1.1
retryExceptions:
- java.lang.RuntimeException
spring:
application:
name: resilience4j
server:
port: 7000
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
3.4 請求重試application啟動類里配置
@Bean
// @LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
3.5 eureka provider的配置
** 聲明一下 用的是eureka server(基本不變)和provider 和resilience4j的請求重試 **
@GetMapping("/hello3")
public String hello3(){
String result = "hello provider:" + port;
// 為了檢測重試策略是否生效
System.out.println("print hello3 func result: " + result);
// 加一個錯誤 測試重試策略
int i = 1 / 0;
return result;
}
3.6 請求重試的配置
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(){
return helloService.hello();
}
}
@Service
// 在這里使用重試策略 yml中backends下配置的
@Retry(name = "retryA")
public class HelloService {
@Autowired
RestTemplate restTemplate;
public String hello(){
return restTemplate.getForObject("http://127.0.0.1:1300/hello3", String.class);
}
}
3.7 啟動運行
啟動eureka server和provider 和請求重試組件
- 訪問http://localhost:7000/hello

- 查看后台provider

3.8 小結
- 總結
- 通過provider提供一個異常 yml配置請求重試4次(重試等待時間 時間間隔 重試異常..) 啟動驗證是否成功
- pom.xml配置那個resilience4j組件 需要注意配置哪個組件 在里面移除哪個組件 若移除的組件未配置 會報錯
4 再見之斷路器
4.1 pom.xml配置
特別說明版本需要改為 1.2.0 不然會報錯 雖然之前的請求重試沒問題
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.2.0</version>
<exclusions>
<!-- 沒有配置的 先排除 不然會報錯 -->
<!-- <exclusion>-->
<!-- <groupId>io.github.resilience4j</groupId>-->
<!-- <artifactId>resilience4j-circuitbreaker</artifactId>-->
<!-- </exclusion>-->
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
</exclusion>
</exclusions>
</dependency>
4.2 yml的配置
resilience4j:
retry:
retry-aspect-order: 133 # 表示Retry優先級(級別高於比如ratelimiter bulkhead timelimiter) 值越小 -> 越大
backends:
# 設置組名
retryA:
# 對比之前的案例
# 重試次數
maxRetryAttempts: 4
# 重試等待
waitDuration: 400
# 間隔乘數(場景: 正好每次間隔為1的時候卡頓 它就有用了 間隔就變了 例如 1 1.1 1.21....)
exponentialBackoffMultiplier: 1.1
retryExceptions:
- java.lang.RuntimeException
# 和之前的maven類似
circuitbreaker:
instances:
nba:
ringBufferSizeInHalfOpenState: 4
ringBufferSizeInClosedState: 4
waitInterval: 4000
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
# 要比上面的值小(先執行當前斷路器)
circuit-breaker-aspect-order: 132
spring:
application:
name: resilience4j
server:
port: 7000
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
4.3 后端配置
之前application啟動類中 已經配置好了RestTemplate controller層沿用之前 只是修改service層
@Service
// 在這里使用重試策略 yml中backends下配置的
//@Retry(name = "retryA")
// name屬性指定CircuitBreaker中yml的配置 fallbackMethod屬性指定服務降級方法
@CircuitBreaker(name = "nba", fallbackMethod = "error")
public class HelloService {
@Autowired
RestTemplate restTemplate;
public String hello(){
return restTemplate.getForObject("http://127.0.0.1:1300/hello3", String.class);
}
// 服務降級方法中 不加參數Throwable 會報錯提示缺少Throwable 要添加異常參數
public String error(Throwable throwable){
return "error";
}
}
4.4 啟動項目
和請求重試的啟動方式是一樣的(重啟/啟動) 訪問網址

4.5 小結
- 和斷路器類似 需要注意的項
- pom.xml中配置的版本號 不然會報錯
- 優先級值的配置 決定誰先運行
- 在service層 配置斷路器 降級方法 需要添加異常參數 否則啟動會報錯缺陷異常
5 再見之限流
5.1 provider
我們要限流 肯定是要從上層限制(比如從廠家那里限制貨品發放量 類似在provider 提供者上進行限制)
- 在provider中添加限流依賴
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.2.0</version>
<exclusions>
<!-- 沒有配置的 先排除 不然會報錯 -->
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</exclusion>
<!--ratelimiter依賴移除-->
<!-- <exclusion>-->
<!-- <groupId>io.github.resilience4j</groupId>-->
<!-- <artifactId>resilience4j-ratelimiter</artifactId>-->
<!-- </exclusion>-->
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</exclusion>
<exclusion>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
</exclusion>
</exclusions>
</dependency>
- yml配置
# 每秒鍾處理2個請求
resilience4j:
ratelimiter:
limiters:
rltA:
limit-for-period: 2
limit-refresh-period: 1s
timeout-duration: 1s
- controller層配置
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/hello")
@RateLimiter(name = "rltA")
public String hello(){
System.out.println(new Date());
return "hello provider:" + port;
}
}
5.2 consumer(消費者)
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello1")
public String hello1(){
return helloService.hello1();
}
}
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
// 限流配置
public String hello1(){
for (int i = 0; i < 5; i++) {
restTemplate.getForObject("http://127.0.0.1:1300/hello", String.class);
}
return "success ratA";
}
}
5.3 運行



