SpringCloud升级之路2020.0.x版-31. FeignClient 实现断路器以及线程隔离限流的思路 (2)

微服务 A 通过同一个线程池调用微服务 B 的所有实例。如果有一个实例有问题,阻塞了请求,或者是响应非常慢。那么久而久之,这个线程池会被发送到这个异常实例的请求而占满,但是实际上微服务 B 是有正常工作的实例的。

为了防止这种情况,也为了限制调用每个微服务实例的并发(也就是限流),我们使用不同线程池调用不同的微服务的不同实例。这个也是通过 resilience4j 实现的。

微服务实例方法粒度的断路器

如果一个实例在一段时间内压力过大导致请求慢,或者实例正在关闭,以及实例有问题导致请求响应大多是 500,那么即使我们有重试机制,如果很多请求都是按照请求到有问题的实例 -> 失败 -> 重试其他实例,这样效率也是很低的。这就需要使用断路器

在实际应用中我们发现,大部分异常情况下,是某个微服务的某些实例的某些接口有异常,而这些问题实例上的其他接口往往是可用的。所以我们的断路器不能直接将这个实例整个断路,更不能将整个微服务断路。所以,我们使用 resilience4j 实现的是微服务实例方法级别的断路器(即不同微服务,不同实例的不同方法是不同的断路器)

使用 resilience4j 的断路器和线程限流器

下面我们先来看下断路器的相关配置,来理解下 resilience4j 断路器的原理:

CircuitBreakerConfig.java

//判断一个异常是否记录为断路器失败,默认所有异常都是失败,这个相当于黑名单 private Predicate<Throwable> recordExceptionPredicate = throwable -> true; //判断一个返回对象是否记录为断路器失败,默认只要正常返回对象就不认为是失败 private transient Predicate<Object> recordResultPredicate = (Object object) -> false; //判断一个异常是否可以不认为是断路器失败,默认所有异常都是失败,这个相当于白名单 private Predicate<Throwable> ignoreExceptionPredicate = throwable -> false; //获取当前时间函数 private Function<Clock, Long> currentTimestampFunction = clock -> System.nanoTime(); //当前时间的单位 private TimeUnit timestampUnit = TimeUnit.NANOSECONDS; //异常名单,指定一个 Exception 的 list,所有这个集合中的异常或者这些异常的子类,在调用的时候被抛出,都会被记录为失败。其他异常不会被认为是失败,或者在 ignoreExceptions 中配置的异常也不会被认为是失败。默认是所有异常都认为是失败。 private Class<? extends Throwable>[] recordExceptions = new Class[0]; //异常白名单,在这个名单中的所有异常及其子类,都不会认为是请求失败,就算在 recordExceptions 中配置了这些异常也没用。默认白名单为空。 private Class<? extends Throwable>[] ignoreExceptions = new Class[0]; //失败请求百分比,超过这个比例,`CircuitBreaker`就会变成`OPEN`状态,默认为 50% private float failureRateThreshold = 50; //当`CircuitBreaker`处于`HALF_OPEN`状态的时候,允许通过的请求数量 private int permittedNumberOfCallsInHalfOpenState = 10; //滑动窗口大小,如果配置`COUNT_BASED`默认值100就代表是最近100个请求,如果配置`TIME_BASED`默认值100就代表是最近100s的请求。 private int slidingWindowSize = 100; //滑动窗口类型,`COUNT_BASED`代表是基于计数的滑动窗口,`TIME_BASED`代表是基于计时的滑动窗口 private SlidingWindowType slidingWindowType = SlidingWindowType.COUNT_BASED; //最小请求个数。只有在滑动窗口内,请求个数达到这个个数,才会触发`CircuitBreaker`对于是否打开断路器的判断。 private int minimumNumberOfCalls = 100; //对应 RuntimeException 的 writableStackTrace 属性,即生成异常的时候,是否缓存异常堆栈 //断路器相关的异常都是继承 RuntimeException,这里统一指定这些异常的 writableStackTrace //设置为 false,异常会没有异常堆栈,但是会提升性能 private boolean writableStackTraceEnabled = true; //如果设置为`true`代表是否自动从`OPEN`状态变成`HALF_OPEN`,即使没有请求过来。 private boolean automaticTransitionFromOpenToHalfOpenEnabled = false; //在断路器 OPEN 状态等待时间函数,默认是固定 60s,在等待与时间后,会退出 OPEN 状态 private IntervalFunction waitIntervalFunctionInOpenState = IntervalFunction.of(Duration.ofSeconds(60)); //当返回某些对象或者异常时,直接将状态转化为另一状态,默认是没有配置任何状态转换机制 private Function<Either<Object, Throwable>, TransitionCheckResult> transitionOnResult = any -> TransitionCheckResult.noTransition(); //当慢调用达到这个百分比的时候,`CircuitBreaker`就会变成`OPEN`状态 //默认情况下,慢调用不会导致`CircuitBreaker`就会变成`OPEN`状态,因为默认配置是百分之 100 private float slowCallRateThreshold = 100; //慢调用时间,当一个调用慢于这个时间时,会被记录为慢调用 private Duration slowCallDurationThreshold = Duration.ofSeconds(60); //`CircuitBreaker` 保持 `HALF_OPEN` 的时间。默认为 0, 即保持 `HALF_OPEN` 状态,直到 minimumNumberOfCalls 成功或失败为止。 private Duration maxWaitDurationInHalfOpenState = Duration.ofSeconds(0);

然后是线程隔离的相关配置:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzgxjg.html