1. 概述
Resilience4j 是一个轻量级的容错库,为 Web 应用提供多种容错和稳定性模式。本文将通过一个简单的 Spring Boot 应用演示如何使用这个库。
2. 项目搭建
本节重点介绍Spring Boot 项目的关键配置。
2.1. Maven 依赖
首先添加 spring-boot-starter-web
依赖来启动 Web 应用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
接着添加 resilience4j-spring-boot2
和 spring-boot-starter-aop
依赖,以便在 Spring Boot 中通过注解使用 Resilience4j 的功能:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
此外,添加 spring-boot-starter-actuator
依赖,通过暴露的接口监控应用状态:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
最后添加 wiremock-jre8
依赖,用于测试 REST API:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<scope>test</scope>
</dependency>
2.2. RestController 和外部 API 调用器
使用 Resilience4j 时,应用需要与外部 API 交互。先创建 RestTemplate
Bean:
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder().rootUri("http://localhost:9090")
.build();
}
定义 ExternalAPICaller
组件类:
@Component
public class ExternalAPICaller {
private final RestTemplate restTemplate;
@Autowired
public ExternalAPICaller(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
}
创建 ResilientAppController
类,暴露 REST 接口并调用外部 API:
@RestController
@RequestMapping("/api/")
public class ResilientAppController {
private final ExternalAPICaller externalAPICaller;
}
2.3. Actuator 接口配置
通过 Spring Boot Actuator 暴露健康检查接口,在 application.properties
中添加:
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.health.circuitbreakers.enabled=true
management.health.ratelimiters.enabled=true
后续会根据需要添加特定功能的配置。
2.4. 单元测试
使用 WireMockExtension
模拟外部服务。在测试类中定义:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ResilientAppControllerUnitTest {
@RegisterExtension
static WireMockExtension EXTERNAL_SERVICE = WireMockExtension.newInstance()
.options(WireMockConfiguration.wireMockConfig()
.port(9090))
.build();
添加 TestRestTemplate
实例:
@Autowired
private TestRestTemplate restTemplate;
2.5. 异常处理器
Resilience4j 会抛出特定异常,需要转换为有意义的 HTTP 状态码。定义 ApiExceptionHandler
类:
@ControllerAdvice
public class ApiExceptionHandler {
}
后续会根据不同容错模式添加处理器。
3. 熔断器
熔断器模式通过限制上游服务调用,保护下游服务。暴露 /api/circuit-breaker
接口并添加 @CircuitBreaker
注解:
@GetMapping("/circuit-breaker")
@CircuitBreaker(name = "CircuitBreakerService")
public String circuitBreakerApi() {
return externalAPICaller.callApi();
}
在 ExternalAPICaller
中实现 callApi()
方法:
public String callApi() {
return restTemplate.getForObject("/api/external", String.class);
}
在 application.properties
中配置熔断器:
resilience4j.circuitbreaker.instances.CircuitBreakerService.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.CircuitBreakerService.minimum-number-of-calls=5
resilience4j.circuitbreaker.instances.CircuitBreakerService.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.instances.CircuitBreakerService.wait-duration-in-open-state=5s
resilience4j.circuitbreaker.instances.CircuitBreakerService.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.CircuitBreakerService.sliding-window-size=10
resilience4j.circuitbreaker.instances.CircuitBreakerService.sliding-window-type=count_based
⚠️ 当失败率达到 50% 时熔断器打开,后续请求抛出 CallNotPermittedException
。添加异常处理器:
@ExceptionHandler({CallNotPermittedException.class})
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public void handleCallNotPermittedException() {
}
测试熔断器行为:
@Test
public void testCircuitBreaker() {
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
.willReturn(serverError()));
IntStream.rangeClosed(1, 5)
.forEach(i -> {
ResponseEntity response = restTemplate.getForEntity("/api/circuit-breaker", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
});
IntStream.rangeClosed(1, 5)
.forEach(i -> {
ResponseEntity response = restTemplate.getForEntity("/api/circuit-breaker", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
});
EXTERNAL_SERVICE.verify(5, getRequestedFor(urlEqualTo("/api/external")));
}
✅ 前 5 次调用失败后熔断器打开,后续请求直接返回 503 状态码。
4. 重试机制
重试模式通过自动重试恢复瞬时故障。添加 /api/retry
接口:
@GetMapping("/retry")
@Retry(name = "retryApi", fallbackMethod = "fallbackAfterRetry")
public String retryApi() {
return externalAPICaller.callApi();
}
提供降级方法:
public String fallbackAfterRetry(Exception ex) {
return "all retries have exhausted";
}
配置重试行为:
resilience4j.retry.instances.retryApi.max-attempts=3
resilience4j.retry.instances.retryApi.wait-duration=1s
resilience4j.retry.metrics.legacy.enabled=true
resilience4j.retry.metrics.enabled=true
测试重试逻辑:
@Test
public void testRetry() {
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
.willReturn(ok()));
ResponseEntity<String> response1 = restTemplate.getForEntity("/api/retry", String.class);
EXTERNAL_SERVICE.verify(1, getRequestedFor(urlEqualTo("/api/external")));
EXTERNAL_SERVICE.resetRequests();
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
.willReturn(serverError()));
ResponseEntity<String> response2 = restTemplate.getForEntity("/api/retry", String.class);
Assert.assertEquals(response2.getBody(), "all retries have exhausted");
EXTERNAL_SERVICE.verify(3, getRequestedFor(urlEqualTo("/api/external")));
}
✅ 正常情况单次调用成功,故障时重试 3 次后触发降级。
5. 超时限制
超时限制模式为异步调用设置超时阈值。添加 /api/time-limiter
接口:
@GetMapping("/time-limiter")
@TimeLimiter(name = "timeLimiterApi")
public CompletableFuture<String> timeLimiterApi() {
return CompletableFuture.supplyAsync(externalAPICaller::callApiWithDelay);
}
模拟延迟调用:
public String callApiWithDelay() {
String result = restTemplate.getForObject("/api/external", String.class);
try {
Thread.sleep(5000);
} catch (InterruptedException ignore) {
}
return result;
}
配置超时限制:
resilience4j.timelimiter.metrics.enabled=true
resilience4j.timelimiter.instances.timeLimiterApi.timeout-duration=2s
resilience4j.timelimiter.instances.timeLimiterApi.cancel-running-future=true
超时后抛出 TimeoutException
,添加处理器:
@ExceptionHandler({TimeoutException.class})
@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
public void handleTimeoutException() {
}
测试超时行为:
@Test
public void testTimeLimiter() {
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
ResponseEntity<String> response = restTemplate.getForEntity("/api/time-limiter", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.REQUEST_TIMEOUT);
EXTERNAL_SERVICE.verify(1, getRequestedFor(urlEqualTo("/api/external")));
}
❌ 调用耗时超过 2 秒时返回 408 状态码。
6. 舱壁隔离
舱壁模式限制外部服务的最大并发调用数。添加 /api/bulkhead
接口:
@GetMapping("/bulkhead")
@Bulkhead(name="bulkheadApi")
public String bulkheadApi() {
return externalAPICaller.callApi();
}
配置舱壁参数:
resilience4j.bulkhead.metrics.enabled=true
resilience4j.bulkhead.instances.bulkheadApi.max-concurrent-calls=3
resilience4j.bulkhead.instances.bulkheadApi.max-wait-duration=1
超出限制时抛出 BulkheadFullException
,添加处理器:
@ExceptionHandler({ BulkheadFullException.class })
@ResponseStatus(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)
public void handleBulkheadFullException() {
}
测试并发限制:
@Test
void testBulkhead() throws Exception {
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
.willReturn(ok()));
Map<Integer, Integer> responseStatusCount = new ConcurrentHashMap<>();
ExecutorService executorService = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(5);
IntStream.rangeClosed(1, 5)
.forEach(i -> executorService.execute(() -> {
ResponseEntity response = restTemplate.getForEntity("/api/bulkhead", String.class);
int statusCode = response.getStatusCodeValue();
responseStatusCount.merge(statusCode, 1, Integer::sum);
latch.countDown();
}));
latch.await();
executorService.shutdown();
assertEquals(2, responseStatusCount.keySet().size());
LOGGER.info("Response statuses: " + responseStatusCount.keySet());
assertTrue(responseStatusCount.containsKey(BANDWIDTH_LIMIT_EXCEEDED.value()));
assertTrue(responseStatusCount.containsKey(OK.value()));
EXTERNAL_SERVICE.verify(3, getRequestedFor(urlEqualTo("/api/external")));
}
✅ 3 个请求成功,其余请求返回 509 状态码。
7. 速率限制
速率限制模式控制对资源的请求速率。添加 /api/rate-limiter
接口:
@GetMapping("/rate-limiter")
@RateLimiter(name = "rateLimiterApi")
public String rateLimitApi() {
return externalAPICaller.callApi();
}
配置速率限制:
resilience4j.ratelimiter.metrics.enabled=true
resilience4j.ratelimiter.instances.rateLimiterApi.register-health-indicator=true
resilience4j.ratelimiter.instances.rateLimiterApi.limit-for-period=5
resilience4j.ratelimiter.instances.rateLimiterApi.limit-refresh-period=60s
resilience4j.ratelimiter.instances.rateLimiterApi.timeout-duration=0s
resilience4j.ratelimiter.instances.rateLimiterApi.allow-health-indicator-to-fail=true
resilience4j.ratelimiter.instances.rateLimiterApi.subscribe-for-events=true
resilience4j.ratelimiter.instances.rateLimiterApi.event-consumer-buffer-size=50
超出限制时抛出 RequestNotPermitted
,添加处理器:
@ExceptionHandler({ RequestNotPermitted.class })
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public void handleRequestNotPermitted() {
}
测试速率限制:
@Test
public void testRatelimiter() {
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
.willReturn(ok()));
Map<Integer, Integer> responseStatusCount = new ConcurrentHashMap<>();
IntStream.rangeClosed(1, 50)
.parallel()
.forEach(i -> {
ResponseEntity<String> response = restTemplate.getForEntity("/api/rate-limiter", String.class);
int statusCode = response.getStatusCodeValue();
responseStatusCount.put(statusCode, responseStatusCount.getOrDefault(statusCode, 0) + 1);
});
assertEquals(2, responseStatusCount.keySet().size());
assertTrue(responseStatusCount.containsKey(TOO_MANY_REQUESTS.value()));
assertTrue(responseStatusCount.containsKey(OK.value()));
EXTERNAL_SERVICE.verify(5, getRequestedFor(urlEqualTo("/api/external")));
}
❌ 5 个请求成功,其余返回 429 状态码。
8. Actuator 监控接口
通过 Actuator 接口监控容错模式行为。首先访问 /actuator
获取所有可用接口:
http://localhost:8080/actuator/
{
"_links" : {
"self" : {...},
"bulkheads" : {...},
"circuitbreakers" : {...},
"ratelimiters" : {...},
...
}
}
查看重试相关接口:
"retries": {
"href": "http://localhost:8080/actuator/retries",
"templated": false
},
"retryevents": {
"href": "http://localhost:8080/actuator/retryevents",
"templated": false
},
"retryevents-name": {
"href": "http://localhost:8080/actuator/retryevents/{name}",
"templated": true
},
"retryevents-name-eventType": {
"href": "http://localhost:8080/actuator/retryevents/{name}/{eventType}",
"templated": true
}
获取重试实例列表:
http://localhost:8080/actuator/retries
{
"retries" : [ "retryApi" ]
}
观察重试事件:
{
"retryEvents": [
{
"retryName": "retryApi",
"type": "RETRY",
"creationTime": "2022-10-16T10:46:31.950822+05:30[Asia/Kolkata]",
"errorMessage": "...",
"numberOfAttempts": 1
},
{
"retryName": "retryApi",
"type": "RETRY",
"creationTime": "2022-10-16T10:46:32.965661+05:30[Asia/Kolkata]",
"errorMessage": "...",
"numberOfAttempts": 2
},
{
"retryName": "retryApi",
"type": "ERROR",
"creationTime": "2022-10-16T10:46:33.978801+05:30[Asia/Kolkata]",
"errorMessage": "...",
"numberOfAttempts": 3
}
]
}
✅ 下游服务故障时,按配置进行 3 次重试,间隔 1 秒。
9. 总结
本文介绍了在 Spring Boot 应用中使用 Resilience4j 的方法,重点演示了熔断器、速率限制、超时限制、舱壁隔离和重试机制等容错模式。完整源代码可在 GitHub 获取。