Netflix的Hystrix微服务容错库已经停⽌更新,官⽅推荐使⽤Resilience4j代替Hystrix,或者使⽤Spring Cloud Alibaba的Sentinel组件。
Resilience4j是受到Netflix Hystrix的启发,为Java8和函数式编程所设计的轻量级容错框架。整个框架只是使⽤了Varr的库,不需要引⼊其他的外部依赖。与此相⽐,Netflix Hystrix对Archaius具有编译依赖,⽽Archaius需要更多的外部依赖,例如Guava和Apache Commons Configuration。
Resilience4j提供了提供了⼀组⾼阶函数(装饰器),包括断路器,限流器,重试机制,隔离机制。你可以使⽤其中的⼀个或多个装饰器对函数式接⼝,lambda表达式或⽅法引⽤进⾏装饰。这么做的优点是你可以选择所需要的装饰器进⾏装饰。
在使⽤Resilience4j的过程中,不需要引⼊所有的依赖,只引⼊需要的依赖即可。
核⼼模块:
resilience4j-circuitbreaker: 熔断
resilience4j-ratelimiter: 限流
resilience4j-bulkhead: 隔离
resilience4j-retry: ⾃动重试
resilience4j-cache: 结果缓存
resilience4j-timelimiter: 超时处理
Hystrix使⽤HystrixCommand来调⽤外部的系统,⽽R4j提供了⼀些⾼阶函数,例如断路器、限流器、隔离机制等,这些函数作为装饰器对函数式接⼝、lambda表达式、函数引⽤进⾏装饰。此外,R4j库还提供了失败重试和缓存调⽤结果的装饰器。你可以在函数式接⼝、
lambda表达式、函数引⽤上叠加地使⽤⼀个或多个装饰器,这意味着隔离机制、限流器、重试机制等能够进⾏组合使⽤。这么做的优点在于,你可以根据需要选择特定的装饰器。任何被装饰的⽅法都可以同步或异步执⾏,异步执⾏可以采⽤ CompletableFuture 或RxJava。
当有很多超过规定响应时间的请求时,在远程系统没有响应和引发异常之前,断路器将会开启。
当Hystrix处于半开状态时,Hystrix根据只执⾏⼀次请求的结果来决定是否关闭断路器。⽽R4j允许执⾏可配置次数的请求,将请求的结果和配置的阈值进⾏⽐较来决定是否关闭断路器。
R4j提供了⾃定义的Reactor和Rx Java操作符对断路器、隔离机制、限流器中任何的反应式类型进⾏装饰。
Hystrix和R4j都发出⼀个事件流,系统可以对发出的事件进⾏监听,得到相关的执⾏结果和延迟的时间统计数据都是⼗分有⽤的。
下⾯分三⽅⾯讲解Resilence4j对于微服务容错的处理,分别为熔断,隔离,限流。
断路器通过有限状态机实现,有三个普通状态:关闭(CLOSED)、开启(OPEN)、半开 (HALF_OPEN),还有两个特殊状态:禁⽤(DISABLED)、强制开启(FORCED_OPEN)。
当熔断器关闭时,所有的请求都会通过熔断器。如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。当经过⼀段时间后,熔断器会从打开状态转换到半开状态,这时仅有⼀定数量的请求会被放⼊,并重新计算失败率,如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。
断路器使⽤滑动窗⼝来存储和统计调⽤的结果。你可以选择基于调⽤数量的滑动窗⼝或者基于时间的滑动窗⼝。基于访问数量的滑动窗⼝统计了最近N次调⽤的返回结果。居于时间的滑动窗⼝统计了最近N秒的调⽤返回结果。
除此以外,熔断器还会有两种特殊状态:DISABLED(始终允许访问)和FORCED_OPEN(始终拒绝访问)。这两个状态不会⽣成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。退出这两个状态的唯⼀⽅法是触发状态转换或者重置熔断器。
配置属性 | 默认值 | 描述 |
failureRateThreshold | 50 | 以百分⽐配置失败率阈值。当失败率等于或⼤于阈值时,断路器状态并关闭变为开启,并进⾏服务降级。 |
slowCallRateThreshold | 100 | 以百分⽐的⽅式配置,断路器把调⽤时间⼤于slowCallDurationThreshold的调⽤视为慢调⽤,当慢调⽤⽐例⼤于等于阈值时,断路器开启,并进⾏服务降级。 |
slowCallDurationThreshold | 60000[ms] | 配置调⽤时间的阈值,⾼于该阈值的呼叫视为慢调⽤,并增加慢调⽤⽐例。 |
permittedNumberOfCallsInHalfOpenState | 10 | 断路器在半开状态下允许通过的调⽤次数。 |
maxWaitDurationInHalfOpenState | 0 | 断路器在半开状态下的最⻓等待时间,超过该配置值的话,断路器会从半开状态恢复为开启状态。配置是0时表示断路器会⼀直处于半开状态,直到所有允许通过的访问结束。 |
slidingWindowType | COUNT_BASED | 配置滑动窗⼝的类型,当断路器关闭时,将调⽤的结果记录在滑动窗⼝中。滑动窗⼝的类型可以是count-based或time-based。如果滑动窗⼝类型是COUNT_BASED,将会统计记录最近slidingWindowSize次调⽤的结果。如果是TIME_BASED,将会统计记录最近 slidingWindowSize秒的调⽤结果。 |
slidingWindowSize | 100 | 配置滑动窗⼝的⼤⼩。 |
minimumNumberOfCalls | 100 | 断路器计算失败率或慢调⽤率之前所需的最⼩调⽤数(每个滑动窗⼝周期)。例如,如果minimumNumberOfCalls为10,则必须⾄少记录10个调⽤,然后才能计算13失败率。如果只记录了9次调⽤,即使所有9次调⽤都失败,断路器也不会开启。 |
waitDurationInOpenState | 60000 [ms] | 断路器从开启过渡到半开应等待的时间。 |
automaticTransition FromOpenToHalfOpenEnabled | false | 如果设置为true,则意味着断路器将⾃动从开启状态过渡到半开状态,并且不需要调⽤来触发转换。创建⼀个线程来监视断路器的所有实例,以便在WaitDurationInOpenstate之后将它们转换为半开状态。但是,如果设置为false,则只有在发出调⽤时才会转换到半开,即使 在waitDurationInOpenState之后也是如此。这⾥的优点是没有线程监视所有断路器的状态。 |
recordExceptions | empty | 记录为失败并因此增加失败率的异常列表。除⾮通过ignoreExceptions显式忽略,否则与列表中某个匹配或继承的异常都将被视为失败。 如果指定异常列表,则所有其他异常均视为成功,除⾮它们被ignoreExceptions显式忽略。 |
ignoreExceptions | empty | 被忽略且既不算失败也不算成功的异常列表。任何与列表之⼀匹配或继承的异常都不会被视为失败或成功,即使异常是 recordExceptions的⼀部分。 |
recordException | throwable -> true By default all exceptions are recored as failures. | ⼀个⾃定义断⾔,⽤于评估异常是否应记录为失败。 如果异常应计为失败,则断⾔必须返回true。如果出断⾔返回false,应算作成功,除⾮ignoreExceptions显式忽略异常。 |
ignoreException | throwable -> false By default no exceptions is ignored. | ⾃定义断⾔来判断⼀个异常是否应该被忽略异常,则谓词必须返回 true。如果异常应算作失败,则断⾔必须返回 false |
参考表属性,在订单项⽬中配置断路器,代码如下。
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowSize: 10 #滑动窗⼝的⼤⼩,配置COUNT_BASED,表示10个请求,配置TIME_BASED表示10秒
minimumNumberOfCalls: 5 #最⼩请求个数,只有在滑动窗⼝内,请求个数达到这个个数,才会触发CircuitBreader对于断路器的判断
permittedNumberOfCallsInHalfOpenState: 3 #当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求个数
automaticTransitionFromOpenToHalfOpenEnabled: true #设置true,表示⾃动从OPEN变成HALF_OPEN,即使没有请求过来
waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
failureRateThreshold: 50 #失败请求百分⽐,超过这个⽐例,CircuitBreaker变为OPEN状态
slowCallDurationThreshold: 2s #慢调⽤时间阈值,⾼于这个阈值的呼叫视为慢调⽤,并增加慢调⽤⽐例。
slowCallRateThreshold: 30 #慢调⽤百分⽐阈值,断路器把调⽤时间⼤于 slowCallDurationThreshold,视为慢调⽤,当慢调⽤⽐例⼤于阈值,断路器打开,并进⾏服务降级
eventConsumerBufferSize: 20 # 表示重试事件缓冲区大小;
recordExceptions: #异常名单 记录为失败并因此增加失败率的异常列表。除⾮通过ignoreExceptions显式忽略,否则与列表中某个匹配或继承的异常都将被视为失败。 如果指定异常列表,则所有其他异常均视为成功,除⾮它们被ignoreExceptions显式忽略。
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
- io.github.robwin.exception.BusinessIoException
ignoreExceptions: # 被忽略且既不算失败也不算成功的异常列表。任何与列表之⼀匹配或继承的异常都不会被视为失败或成功,即使异常是 recordExceptions的⼀部分。
- io.github.robwin.exception.BusinessException
shared:
slidingWindowSize: 100
permittedNumberOfCallsInHalfOpenState: 30
waitDurationInOpenState: 1s
failureRateThreshold: 50
eventConsumerBufferSize: 10
ignoreExceptions:
- io.github.robwin.exception.BusinessException
instances:
backendA:
baseConfig: default
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 10
permittedNumberOfCallsInHalfOpenState: 3
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordExceptions:
- io.github.robwin.exception.BusinessIoException
recordFailurePredicate: io.github.robwin.exception.RecordFailurePredicate
上⾯配置了2个断路器"backendA",和"backendB",其中backendA断路器配置基于default配置,"backendB"断路器配置了慢调⽤⽐例熔断,"backendA"熔断器配置了异常⽐例熔断
4.OrderController
@RestController
@RequestMapping(value = "/backendC")
public class BackendCController {
private final Service businessCService;
public BackendCController(@Qualifier("backendCService")Service businessCService){
this.businessCService = businessCService;
}
@GetMapping("failure")
public String failure(){
return businessCService.failure();
}
}
@Component(value = "backendCService")
public class BackendCService implements Service {
private static final String BACKEND_C = "backendC";
@Override
@CircuitBreaker(name = BACKEND_C)
@Bulkhead(name = BACKEND_C)
@Retry(name = BACKEND_C)
public String failure() {
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "This is a remote exception");
}
}