resilience4j + Spring 断路器实例

resilience4j + Spring instance of CircuitBreaker

我想使用Resilience4j来处理容错,我正在使用CircuitBreaker和TimerLimit。

我想分离容错行为的业务逻辑,而不是"dirty"我的业务代码。

所以,我正在考虑使用命令模式来执行我将要处理的方法,就像 Hystrix 对 HystrixCommand 所做的那样。

示例:

public class MyCommand {

    private static final CircuitBreaker circuitBreaker;
    private Long param1, param2;
    private MyService myService;
    private static final TimeLimiter timeLimiter;

    static {
        long ttl = 50000;
        TimeLimiterConfig configTimerLimit
                = TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(ttl)).build();
        timeLimiter = TimeLimiter.of(configTimerLimit);

        // I got the configuration from a class that I created.
        circuitBreaker = CircuitBreaker.of("my", CircuitBreakerConfigOptions.defaultForExternalService());
    }

    public MyCommand(Long param1, Long param2, MyService myService) {
        this.param1 = param1;
        this.param2 = param2;
        this.myService = myService;
    }

    public String run() {
        Callable<String> stringCallable = TimeLimiter.decorateFutureSupplier(timeLimiter,
                () -> CompletableFuture.supplyAsync(() -> myService.hello(param1, param2)));

        Callable<String> callable = CircuitBreaker.decorateCallable(circuitBreaker, stringCallable);

        return Try.of(callable::call).recover(t -> fallback(t)).get();
    }

    protected String fallback(Throwable throwable) {
        Callable<String> stringCallable = TimeLimiter.decorateFutureSupplier(timeLimiter,
                () -> CompletableFuture.supplyAsync(() -> myService.otherHello(param1, param2)));

        return Try.of(stringCallable::call).getOrElse("Fallback");
    }
}

调用我的控制器:

@ApiOperation(value = "Only to test")
@GetMapping(value = "/execute", produces = MediaType.APPLICATION_JSON)
public String execute() {
    return new MyCommand(1L, 2L, new MyService()).run();
}

我的疑惑:

1 - 在这种情况下,circuitBreaker 确实需要是静态的,因为我知道同一个对象需要在您想要威胁的同一个方法之间共享,我说得对吗?

2 - 我如何拥有此应用程序的多个实例,断路器对每个实例单独工作?我说的对吗?

因为您似乎正在使用 Spring 启动,您可以使用 resilience4j-spring-boot-2 启动模块,它也支持注释。

https://resilience4j.readme.io/docs/getting-started-3

我从你的问题中了解到 - 你需要一个 Resilience4j 的断路器,它应该是独立的,即不与你的业务逻辑混淆。

所以我建议在 运行() 方法周围放置断路器保护。下面的代码将详细说明 -

您的控制器 -

@ApiOperation(value = "Only to test")
@GetMapping(value = "/execute", produces = MediaType.APPLICATION_JSON)
public String execute() {
    return new MyCommand().run();  
}

现在用@CircuitBreaker

编写 MyCommand class 的 运行() 方法
public class MyCommand {

    @CircuitBreaker(name = "RUN_METHOD_PROTECTION")        // <---- here is our circuit breaker annotation code top of below your business code... and that’s it.
    Your_Response run(Your_Request){
        // Your business logic written here...
    }

在您的 YAML 属性 文件中进一步添加断路器配置,如下所示(我使用的是基于计数而不是基于时间)–

resilience4j.circuitbreaker:
  backends:
    RUN_METHOD_PROTECTION:
      registerHealthIndicator: true
      slidingWindowSize: 100                     # start rate calc after 100 calls
      minimumNumberOfCalls: 100                  # minimum calls before the CircuitBreaker can calculate the error rate.
      permittedNumberOfCallsInHalfOpenState: 10  # number of permitted calls when the CircuitBreaker is half open
      waitDurationInOpenState: 10s               # time that the CircuitBreaker should wait before transitioning from open to half-open
      failureRateThreshold: 50                   # failure rate threshold in percentage
      slowCallRateThreshold: 100                 # consider all transactions under interceptor for slow call rate
      slowCallDurationThreshold: 2s              # if a call is taking more than 2s then increase the error rate
      recordExceptions:                          # increment error rate if following exception occurs
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
        - org.springframework.web.client.ResourceAccessException

现在,如果您无法在您的项目中使用注解@CircuitBreaker,那么您也可以以功能方式进行操作,即

假设我们在您的配置中定义了一个 bean,

 @Bean
 public CircuitBreaker MyCircuitBreaker(){

     CircuitBreakerConfig config = CircuitBreakerConfig.custom()
             .slidingWindow(100,100, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
             .failureRateThreshold(50)
             .build();
     CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
     CircuitBreaker circuitBreaker = registry.circuitBreaker("your_run_method_CircuitBreker");  // here you are registering your circuit breaker with a unique tag. And in future you refer this tag you get a same circuit breaker.      
     return circuitBreaker;
 }

现在你的控制器代码会在下面 -

private CircuitBreaker circuitBreaker;  // Assume you have injected the value from CircuitBreaker bean 

@ApiOperation(value = "Only to test")
@GetMapping(value = "/execute", produces = MediaType.APPLICATION_JSON)
public String execute() {
    Function<Your_Request, Your_Response> decorated = CircuitBreaker
                 .decorateFunction(circuitBreaker, new MyCommand().run());

    return decorated.apply();
}

这样你也不会干扰你的业务逻辑。