Resilience4j是一个轻量级、易于使用的轻量级“容错”包。它受Neflix Hystrix启发但只有一个依赖(Vavr),而不像Hystrix很多很多的依赖。
Resilience4j在“容错”方面提供了各种模式:断路器(Circuit Breaker)、重试(Retry)、限时器(Time Limiter)、限流器(Rate Limiter)、隔板(BulkHead)。
在Spring Boot下,Resilience4j比Hystrix更适合用在容错的各种模式下。我们只要在程序中使用简单的注解即可实现。
那我们新建一个演示项目来演示功能:
暂时的依赖只需要Spring Web、Spring Boot Actuator。
在Spring Boot下使用Resilience4j,需要加上下面两个依赖
implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.1'
implementation 'org.springframework.boot:spring-boot-starter-aop'
io.github.resilience4j
resilience4j-spring-boot2
1.7.1
org.springframework.boot
spring-boot-starter-aop
断路器来自于生活中的断路器,是指当电流超过规定值时,以本身产生的热量使熔体熔断,断开电路的一种电器。
软件开发中的断路器有三个状态:
关闭(CLOSED):正常情况,所有的请求都正常通过断路器,没有任何限制。
打开(OPEN):在过去的请求或者时间中,如果故障或者慢的响应率大于或者等于一个配置的阈值,断路器就会打开。在这种情况下,所有的请求都会受到限制。
半开(HALF_OPEN):在打开状态下经过可配置的等待时间后,断路器允许少量(数值可配置)的请求通过。若失败/慢响应超过阈值,断路器重新打开;低于阈值,则断路器进入关闭状态。
断路器通过限制上游服务调用,在下游服务在部分或者全部停止服务的情况下,对下游服务进行保护。
我们新建控制器,并在当前的控制器上编写断路器的相关代码:
@RestController
public class Resilience4jController {
private final RestTemplate restTemplate;
public Resilience4jController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
@GetMapping("/circuit-breaker")
@CircuitBreaker(name = "circuitBreakerDemo")
public String circuitBreaker(){
return restTemplate.getForObject("http://no-such-site/api/external", String.class);
}
}
我们在控制器方法circuitBreaker() 调用下游的服务,本例演示中的下游服务并不存在。
我们可以简单的在方法上使用@CircuitBreaker 注解即可使用Resilience4j提供的断路器,name 中定义的circuitBreakerDemo 是当前断路器的名称。
我们可以通过application.properties 对断路器的具体行为进行配置,instances 后面的名称是在@CircuitBreaker 中配置的名字:
resilience4j.circuitbreaker.instances.circuitBreakerDemo.sliding-window-type=COUNT_BASED
resilience4j.circuitbreaker.instances.circuitBreakerDemo.sliding-window-size=10
resilience4j.circuitbreaker.instances.circuitBreakerDemo.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.circuitBreakerDemo.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.circuitBreakerDemo.minimum-number-of-calls=5
resilience4j.circuitbreaker.instances.circuitBreakerDemo.wait-duration-in-open-state=5s
1、sliding-window-type :断路器的滑动窗口期类型可以基于“次数”(COUNT_BASED)或者“时间”(TIME_BASED)进行熔断,默认是COUNT_BASED。
2、failure-rate-threshold :设置50%的调用失败时打开断路器。
3、sliding-window-size :若COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器;若为TIME_BASED则,此时有额外的两个设置属性,含义为:在*秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过2秒(slow-call-duration-threshold)打开断路器。
resilience4j.circuitbreaker.instances.circuitBreakerDemo.slow-call-rate-threshold=100
resilience4j.circuitbreaker.instances.circuitBreakerDemo.slow-call-duration-threshold=2000
4、permitted-number-of-calls-in-half-open-state :运行断路器在HALF_OPEN状态下时进行3次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态。
5、minimum-number-of-calls :在每个滑动窗口期,配置断路器计算错误率或者慢调用率的最小调用数。本例中设置的5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态。
6、wait-duration-in-open-state :一旦断路器是打开状态,它会拒绝请求5秒钟,然后转入半开状态。
更多关于断路器的配置可参考官方文档:https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker
在application.properties 里添加相关的配置:
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
#显示断路器的健康状态
management.health.circuitbreakers.enabled=true
resilience4j.circuitbreaker.instances.circuitBreakerDemo.register-health-indicator=true
当断路器处于打开和半开状态时,会抛出CallNotPermittedException 异常,我们可以来进行全局处理。
@RestControllerAdvice
public class Resilience4jExceptionHandler {
@ExceptionHandler({ CallNotPermittedException.class })
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public String handleCallNotPermittedException() {
return "调用不被允许";
}
}
启动程序,用postman访问:http://localhost:8080/circuit-breaker,调用5次。第6次,断路器被打开:
访问:http://localhost:8080/actuator/health
访问:http://localhost:8080/actuator/metrics
我们可以继续访问:
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.buffered.calls
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.calls
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.failure.rate
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.not.permitted.calls
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.slow.call.rate
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.slow.call
http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.state
http://localhost:8080/actuator/circuitbreakerevents
http://localhost:8080/actuator/circuitbreakerevents/{name}/{eventType}
http://localhost:8080/actuator/circuitbreakerevents/{name}
http://localhost:8080/actuator/circuitbreakers
http://localhost:8080/actuator/circuitbreakers/{name}
重试在远程服务调用时是必须的能力。我们在控制器中添加演示重试的控制器方法:
@RestController
public class Resilience4jController {
private final RestTemplate restTemplate;
public Resilience4jController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
@GetMapping("/retry")
@Retry(name = "retryDemo" , fallbackMethod = "fallback")
public String retry() {
return restTemplate.getForObject("http://no-such-site/api/external", String.class);
}
public String fallback(Exception exception){
return "系统故障,无法访问";
}
}
同样,我们可以用@Retry 注解轻松使用Resilience4j的重试能力。
同样,我们可以在application.properties 中配置重试。
resilience4j.retry.instances.retryDemo.max-attempts=3
resilience4j.retry.instances.retryDemo.wait-duration=1s
resilience4j.retry.metrics.legacy.enabled=true
resilience4j.retry.metrics.enabled=true
1、max-attempts:最大尝试重试次数。
2、wait-duration: 每次重试之间的间隔时间。
更多的重试配置,请参考官网:https://resilience4j.readme.io/docs/retry
访问:http://localhost:8080/retry ,重试后,进入后备方法fallback 。
/actuator/metrics
/actuator/metrics/{requiredMetricName}
/actuator/retries
/actuator/retryevents
/actuator/retryevents/{name}
/actuator/retryevents/{name}/{eventType}
隔板来自造船行业,床仓内部一般会分成很多小隔舱,一旦一个隔舱漏水因为隔板的存在而不至于影响其它隔舱和整体船。
隔板(Bulkhead)可以用来限制对于下游服务的最大并发数量的限制。
我们在控制器中添加方法来演示隔板:
@GetMapping("/bulkhead")
@Bulkhead(name = "bulkheadDemo")
public String bulkhead() {
return restTemplate.getForObject("https://reqres.in/api/users", String.class);
}
使用@Bulkhead 注解实现隔板功能,在application.properties 中配置隔板:
resilience4j.bulkhead.instances.bulkheadDemo.max-concurrent-calls=3
resilience4j.bulkhead.instances.bulkheadDemo.max-wait-duration=1
resilience4j.bulkhead.metrics.enabled=true
1、max-concurrent-calls :隔板允许的最大并发执行数量
2、max-wait-duration :当试图进入一个饱和的隔板时,线程应被阻断的最大时间。
Resilience4j的隔板支持两种类型:
默认的Semaphore:使用用户请求的线程,而不创建新的线程。
线程池:创建新的线程用来处理。配置如:
resilience4j.thread-pool-bulkhead.instances.bulkheadDemo.max-thread-pool-size= 3
resilience4j.thread-pool-bulkhead.instances.bulkheadDemo.core-thread-pool-size=2
resilience4j.thread-pool-bulkhead.instances.bulkheadDemo.queue-capacity=1
更多隔板的配置,参考官网:https://resilience4j.readme.io/docs/bulkhead
当隔板饱和的时候会抛出BulkheadFullException 异常,添加到Resilience4jExceptionHandler。
@ExceptionHandler({ BulkheadFullException.class })
@ResponseStatus(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)
public String handleBulkheadFullException() {
return "隔板已满";
}
用ApiFox访问:http://localhost:8080/bulkhead,并发设置为4,得到结果:
我们看下失败请求的明细:
/actuator/metrics
/actuator/metrics/{requiredMetricName}
/actuator/bulkheads
/actuator/bulkheadevents
/actuator/bulkheadevents/{name}
/actuator/bulkheadevents/{name}/{eventType}
限流器用来限制访问下游服务的速度。控制器中添加演示方法:
@GetMapping("/rate-limiter")
@RateLimiter(name = "rateLimitDemo")
public String rateLimit() {
return restTemplate.getForObject("https://reqres.in/api/users", String.class);
}
同样限流只需要使用注解@RateLimiter 即可。在application.properties 添加配置:
resilience4j.ratelimiter.instances.rateLimitDemo.limit-for-period=5
resilience4j.ratelimiter.instances.rateLimitDemo.limit-refresh-period=60s
resilience4j.ratelimiter.instances.rateLimitDemo.timeout-duration=0s
resilience4j.ratelimiter.instances.rateLimitDemo.allow-health-indicator-to-fail=true
resilience4j.ratelimiter.instances.rateLimitDemo.subscribe-for-events=true
resilience4j.ratelimiter.instances.rateLimitDemo.event-consumer-buffer-size=50
resilience4j.ratelimiter.metrics.enabled=true
resilience4j.ratelimiter.instances.rateLimitDemo.register-health-indicator=true
上面的配置限制调用的限制是每60秒(limit-refresh-period )的访问次数是5次(limit-for-period )。
更多关于限流器的配置请参考官网:https://resilience4j.readme.io/docs/ratelimiter
当达到允许的速度的时候,请求将会被拒绝,程序抛出RequestNotPermitted 异常。
@ExceptionHandler({ RequestNotPermitted.class })
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public String handleRequestNotPermitted() {
return "请求不被允许";
}
访问:http://localhost:8080/rate-limiter,在60秒内访问次数超过5次:
/actuator/health
/actuator/metrics
/actuator/metrics/{requiredMetricName}
/actuator/ratelimiters
/actuator/ratelimiterevents
/actuator/ratelimiterevents/{name}
/actuator/ratelimiterevents/{name}/{eventType}
限时器用来限制在另外一个线程中执行的服务调用的时间。在控制器中新建方法,我们需要调用一个异步服务,即在另一个线程中执行的服务:
RestController
public class Resilience4jController {
private final RestTemplate restTemplate;
private final AsyncService asyncService;
public Resilience4jController(RestTemplateBuilder builder, AsyncService asyncService) {
this.restTemplate = builder.build();
this.asyncService = asyncService;
}
@GetMapping("/time-limiter")
@TimeLimiter(name = "timeLimiterDemo")
public CompletableFuture timeLimiter() {
return asyncService.doSomething();
}
}
通过@TimeLimiter 注解使用显示器功能,我们程序还需使用@EnableAsync 开启异步的支持:
@SpringBootApplication
@EnableAsync
public class Resilience4jDemoApplication {
public static void main(String[] args) {
SpringApplication.run(Resilience4jDemoApplication.class, args);
}
}
异步服务:
@Service
public class AsyncService {
private final RestTemplate restTemplate;
public AsyncService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
@Async
public CompletableFuture doSomething() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new AsyncResult(restTemplate.getForObject("https://reqres.in/api/users", String.class)).completable();
}
}
在这个异步线程任务里,线程睡眠了3秒钟。
我们在application.properties中对限时器进行配置:
resilience4j.timelimiter.instances.timeLimiterDemo.timeout-duration=2s
resilience4j.timelimiter.instances.timeLimiterDemo.cancel-running-future=true
resilience4j.timelimiter.metrics.enabled=true
配置意味着当异步线程处理时间超过2s(timeout-duration )后将会限制访问。上面异步线程处理中线程睡了3秒,那请求必然超时。
更多关于限时器的配置,请查看官网:https://resilience4j.readme.io/docs/timeout
当异步处理线程超时后,将会抛出TimeoutException 异常。
@ExceptionHandler({ TimeoutException.class })
@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
public String handleTimeoutException() {
return "访问超时";
}
访问:http://localhost:8080/time-limiter,提示访问超时。
/actuator/metrics
/actuator/metrics/{requiredMetricName}
/actuator/timelimiters
/actuator/timelimiterevents
/actuator/timelimiterevents/{name}
/actuator/timelimiterevents/{name}/{eventType}
感谢对我的书《从企业级开发到云原生微服务:Spring Boot实战》的支持。
转载请注明出处:今日头条:爱科学的卫斯理
(此处已添加书籍卡片,请到今日头条客户端查看)页面更新:2024-03-21
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号