代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-gateway/src/test/java/com/kawa/spbgateway/circuitbreaker/resilience4j

公司的网关(基于Spring Cloud Gateway)上线有一段时间了,目前只有一个简单的动态路由的功能,接下来的工作一部分会涉及到服务的保护和服务健壮性方面,也就是要加入限流,熔断和降级等特性。此处找了下业界成熟的开源框架如下表的对比

  Sentinel(Alibaba开源) Hystrix(不再维护) resilience4j(Spring官方推荐)
隔离策略 信号量隔离(并发控制) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于慢调用比例、异常比例、异常数 基于异常比例 基于异常比例、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式与匀速排队控制效果 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
多语言支持 Java/Go/C++ Java Java
Service Mesh 支持 支持 Envoy/Istio 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、实时监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统

对比来自:https://github.com/alibaba/Sentinel/wiki/Guideline:-%E4%BB%8E-Hystrix-%E8%BF%81%E7%A7%BB%E5%88%B0-Sentinel

最终基于公司的需求,准备引入Resilience4j组件, 所以这篇博客是来梳理Resilience4j的组件的使用方式, 下一篇博客写结合Spring Cloud Gateway的实现自定义的服务限流保护策略

1. Resilience4j

Resilience4j官方guide: https://resilience4j.readme.io/docs

Resilience4j 常用的组件有5个 -> CircuitBreakerBulkheadRateLimiterRetry 和 TimeLimiter (Cache不推荐在生产环境使用,所以这篇博客不做介绍 ), 本篇博客基于1.7.0的版本介绍

1.1 CircuitBreaker

断路器是通过具有三个正常状态的有限状态机实现的:CLOSED、OPEN 和 HALF_OPEN 以及两个特殊状态 DISABLED 和 FORCED_OPEN。CircuitBreaker 使用滑动窗口来存储和聚合调用的结果。您可以在基于计数的滑动窗口和基于时间的滑动窗口之间进行选择。基于计数的滑动窗口聚合最后 N 次调用的结果。基于时间的滑动窗口聚合了最近 N 秒的调用结果。

1.1.1 CircuitBreakerConfig

CircuitBreakerConfig看名字大家也知道了它是做什么的(好的编码就是见文知意),CircuitBreaker的配置类,在实际项目中除了全局的配置,有些场景需要我们自定义一些CircuitBreaker的配置,这个时候就需要用到Circuitreakeronfig,Circuitreakeronfig全部属性如下表

配置属性 默认值 描述
failureRateThreshold 50 以百分比形式配置失败率阈值。
当故障率等于或大于阈值时,断路器转换为断开并开始短路调用。
slowCallRateThreshold 100 以百分比配置阈值。当呼叫持续时间大于 或等于阈值时,断路器将呼叫视为慢速呼叫。当慢速呼叫的百分比等于或大于阈值时,断路器转换为断开并开始短路呼叫。slowCallDurationThreshold
slowCallDurationThreshold 60000 [毫秒] 配置持续时间阈值,该数值的呼叫速度缓慢并增加呼叫的速度。
permittedNumberOfCalls
InHalfOpenState
10 配置半开时允许的呼叫数量。
maxWaitDurationInHalfOpenState 0 [毫秒] 配置最大等待持续时间,控制断路器在切换到打开状态之前可以保持在半开状态的最长时间。
值 0 表示断路器将在 HalfOpen 状态无限等待,直到所有允许的调用都完成。
slidingWindowType COUNT_BASED 配置用于记录CircuitBreaker关闭时调用结果的滑动窗口的类型。
滑动窗口可以是基于计数的,也可以是基于时间的。
如果滑动窗口为 COUNT_BASED,则记录并汇总最后一次调用。 如果滑动窗口是 TIME_BASED,则记录和聚合最后几秒的调用。slidingWindowSize
slidingWindowSize
slidingWindowSize 100 配置用于记录关闭时调用窗口的窗口大小。
minimumNumberOfCalls 100 配置在断路器计算错误率或慢速调用率之前所需的最小调用数(每个滑动窗口周期)。
例如,如果minimumNumberOfCalls为10,则必须至少记录10个呼叫,然后才能计算失败率。
如果仅记录了9个呼叫,则即使有9个呼叫都失败,断路器也不会转换为打开状态。
waitDurationInOpenState 60000 [毫秒] 半从打开转换到打开之前应等待的时间。
automaticTransition
FromOpenToHalfOpenEnabled
FALSE 如果设置为 true,则意味着 CircuitBreaker 将自动从打开状态转换为半打开状态,并且不需要调用来触发转换。创建一个线程来监视 CircuitBreakers 的所有实例,一旦 waitDurationInOpenState 通过,将它们转换为 HALF_OPEN。然而,如果设置为 false,则仅在进行调用时才会转换到 HALF_OPEN,即使在传递了 waitDurationInOpenState 之后也是如此。这里的优点是没有线程监视所有断路器的状态。
recordExceptions empty 记录为失败并因此增加失败率的异常列表。
任何匹配或从列表之一继承的异常都算作失败,除非通过。 如果您指定异常列表,则所有其他异常都算作成功,除非它们被明确忽略。ignoreExceptions
ignoreExceptions empty 被忽略且既不计为失败也不计为成功的异常列表。
即使异常是。recordExceptions
recordFailurePredicate throwable -> true
默认情况下,所有异常都记录为失败。
一个自定义Predicate,用于评估是否应将异常记录为失败。
如果异常应算作失败,则谓词必须返回 true。如果异常
应算作成功,则谓词必须返回 false,除非异常被 显式忽略。ignoreExceptions
ignoreExceptions throwable -> false
默认情况下不会忽略任何异常。
一个自定义Predicate,用于评估是否应忽略异常并且既不视为失败也不成功。
如果应忽略异常,谓词必须返回 true。
如果异常应算作失败,则谓词必须返回 false。

1.1.2 CircuitBreakerRegistry

CircuitBreakerRegistry是CircuitBreaker的注册器,其有一个唯一的实现类InMemoryCircuitBreakerRegistry,核心方法如下

// 根据name返回CircuitBreaker或返回默认的CircuitBreaker
// 下面的几个重载的方法,也是一样的逻辑,有就直接返回,没有就创建后返回
public CircuitBreaker circuitBreaker(String name)

public CircuitBreaker circuitBreaker(String name, io.vavr.collection.Map<String, String> tags)

public CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig config) {

public CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig config, io.vavr.collection.Map<String, String> tags)

public CircuitBreaker circuitBreaker(String name, String configName)

public CircuitBreaker circuitBreaker(String name, String configName, io.vavr.collection.Map<String, String> tags) {

public CircuitBreaker circuitBreaker(String name, Supplier<CircuitBreakerConfig> circuitBreakerConfigSupplier)

public CircuitBreaker circuitBreaker(String name, Supplier<CircuitBreakerConfig> circuitBreakerConfigSupplier, io.vavr.collection.Map<String, String> tags)

1.1.3 CircuitBreaker

现在到了我们的核心接口CircuitBreaker,下面的静态方法有20多个,在这我就列几个常用的方法,其它方法可以看源码注释的描述

// 返回一个被CircuitBreaker包装的 CheckedFunction0.
// CheckedFunction0 是由vavr封装的类似java8中Supplier的函数
static <T> CheckedFunction0<T> decorateCheckedSupplier(CircuitBreaker circuitBreaker, CheckedFunction0<T> supplier) // 返回一个被CircuitBreaker包装的 CheckedRunnable.
//CheckedRunnable 是由avr封装的 Runnable
static CheckedRunnable decorateCheckedRunnable(CircuitBreaker circuitBreaker, CheckedRunnable runnable) // 返回一个被CircuitBreaker包装的 Callable.
static <T> Callable<T> decorateCallable(CircuitBreaker circuitBreaker, Callable<T> callable) // 返回一个被CircuitBreaker包装的 Supplier.
static <T> Supplier<T> decorateSupplier(CircuitBreaker circuitBreaker, Supplier<T> supplier) // 返回一个可以retry的 Supplierstatic <T> Supplier<Try<T>> decorateTrySupplier(CircuitBreaker circuitBreaker, Supplier<Try<T>> supplier) // 返回一个被CircuitBreaker包装的 Consumer.
static <T> Consumer<T> decorateConsumer(CircuitBreaker circuitBreaker, Consumer<T> consumer) // 返回一个被CircuitBreaker包装的 CheckedConsumer.
// CheckedConsumer 是由avr封装的CheckedConsumer
static <T> CheckedConsumer<T> decorateCheckedConsumer(CircuitBreaker circuitBreaker, CheckedConsumer<T> consumer) // 返回一个被CircuitBreaker包装的 Runnable.
static Runnable decorateRunnable(CircuitBreaker circuitBreaker, Runnable runnable) // 返回一个被CircuitBreaker包装的 Function.
static <T, R> Function<T, R> decorateFunction(CircuitBreaker circuitBreaker, Function<T, R> function) // 返回一个被CircuitBreaker包装的 CheckedFunction1.
// CheckedFunction1是由avr封装的Function
static <T, R> CheckedFunction1<T, R> decorateCheckedFunction(CircuitBreaker circuitBreaker, CheckedFunction1<T, R> function) // 返回一个被CircuitBreaker包装的 Supplier<Future>.
static <T> Supplier<Future<T>> decorateFuture(CircuitBreaker circuitBreaker, Supplier<Future<T>> supplier)

从上面列举的常用方法看到有很多好像有重复的方法,CircuitBreaker有返回封装Supplier, Consumer, Function, Runnable的方法,然后还有一个与之对应的返回封装CheckedSupplier, CheckedConsumer, CheckedFunction, CheckedRunnable的方法。 为什么有两套实现呢?resilience4j,这个项目是基于Java 8开发的,但是java8受限于 Java 标准库的通用性要求和二进制文件大小,Java 标准库对函数式编程的 API 支持相对比较有限。函数的声明只提供了 Function 和 BiFunction 两种,流上所支持的操作的数量也较少。基于这些原因,需要vavr 来更好得使用Java 8进行函数式开发。

简单看下方法decorateCheckedSupplier(CircuitBreaker circuitBreaker, CheckedFunction0<T> supplier)

    static <T> CheckedFunction0<T> decorateCheckedSupplier(CircuitBreaker circuitBreaker,
CheckedFunction0<T> supplier) {
return () -> {
     // 申请执行函数方法supplier.apply()的许可
// 具体逻辑在CircuiBreakerStateMachine中的CircuitBreakerState中实现    
circuitBreaker.acquirePermission();
final long start = circuitBreaker.getCurrentTimestamp();
try {
          // 执行目标方法
T result = supplier.apply();
long duration = circuitBreaker.getCurrentTimestamp() - start;
//目标方法执行完调用onResult(),check result最终调用onSuccess()
circuitBreaker.onResult(duration, circuitBreaker.getTimestampUnit(), result);
return result;
} catch (Exception exception) {
// Do not handle java.lang.Error
long duration = circuitBreaker.getCurrentTimestamp() - start;
// 如果出现异常就调用onError(),执行onError策略的逻辑
circuitBreaker.onError(duration, circuitBreaker.getTimestampUnit(), exception);
throw exception;
}
};
}

大体流程如下图

关于vavr的详情可以查看官网文档:https://docs.vavr.io/

CircuitBreaker唯一的实现类CircuitBreakerStateMachine

CircuitBreakerStateMachine是一个有线状态的状态机。断路器管理后端系统的状态。断路器通过具有五种状态的有限状态机实现:CLOSED、OPEN、HALF_OPEN、DISABLED 和 FORCED_OPEN。 CircuitBreakerStateMachine可以做到这些状态的转换,比如下面的几个方法

@Override
public void transitionToDisabledState() {
stateTransition(DISABLED, currentState -> new DisabledState());
} @Override
public void transitionToMetricsOnlyState() {
stateTransition(METRICS_ONLY, currentState -> new MetricsOnlyState());
} @Override
public void transitionToForcedOpenState() {
stateTransition(FORCED_OPEN,
currentState -> new ForcedOpenState(currentState.attempts() + 1));
} @Override
public void transitionToClosedState() {
stateTransition(CLOSED, currentState -> new ClosedState());
} @Override
public void transitionToOpenState() {
stateTransition(OPEN,
currentState -> new OpenState(currentState.attempts() + 1, currentState.getMetrics()));
} @Override
public void transitionToHalfOpenState() {
stateTransition(HALF_OPEN, currentState -> new HalfOpenState(currentState.attempts()));
}

这些状态的流转是通过发布事件来完成的,可以看下面都是CircuitBreakerStateMachine的事件

private void publishResetEvent() {
final CircuitBreakerOnResetEvent event = new CircuitBreakerOnResetEvent(name);
publishEventIfPossible(event);
} private void publishCallNotPermittedEvent() {
final CircuitBreakerOnCallNotPermittedEvent event = new CircuitBreakerOnCallNotPermittedEvent(
name);
publishEventIfPossible(event);
} private void publishSuccessEvent(final long duration, TimeUnit durationUnit) {
final CircuitBreakerOnSuccessEvent event = new CircuitBreakerOnSuccessEvent(name,
Duration.ofNanos(durationUnit.toNanos(duration)));
publishEventIfPossible(event);
} private void publishCircuitErrorEvent(final String name, final long duration,
TimeUnit durationUnit, final Throwable throwable) {
final CircuitBreakerOnErrorEvent event = new CircuitBreakerOnErrorEvent(name,
Duration.ofNanos(durationUnit.toNanos(duration)), throwable);
publishEventIfPossible(event);
} private void publishCircuitIgnoredErrorEvent(String name, long duration, TimeUnit durationUnit,
Throwable throwable) {
final CircuitBreakerOnIgnoredErrorEvent event = new CircuitBreakerOnIgnoredErrorEvent(name,
Duration.ofNanos(durationUnit.toNanos(duration)), throwable);
publishEventIfPossible(event);
} private void publishCircuitFailureRateExceededEvent(String name, float failureRate) {
final CircuitBreakerOnFailureRateExceededEvent event = new CircuitBreakerOnFailureRateExceededEvent(name,
failureRate);
publishEventIfPossible(event);
} private void publishCircuitSlowCallRateExceededEvent(String name, float slowCallRate) {
final CircuitBreakerOnSlowCallRateExceededEvent event = new CircuitBreakerOnSlowCallRateExceededEvent(name,
slowCallRate);
publishEventIfPossible(event);
} private void publishCircuitThresholdsExceededEvent(Result result, CircuitBreakerMetrics metrics) {
if (Result.hasFailureRateExceededThreshold(result)) {
publishCircuitFailureRateExceededEvent(getName(), metrics.getFailureRate());
}
if (Result.hasSlowCallRateExceededThreshold(result)) {
publishCircuitSlowCallRateExceededEvent(getName(), metrics.getSlowCallRate());
}
}

1.1.4 CircuitBreaker Demo

引入测试组件spring-cloud-starter-contract-stub-runner

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>

Resilience4jTestHelper测试辅助类

    /**
* get the CircuitBreaker status and metrics
*
* @param prefixName
* @param circuitBreaker
* @return circuitBreaker state
*/
public static String getCircuitBreakerStatus(String prefixName, CircuitBreaker circuitBreaker) { CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
float failureRate = metrics.getFailureRate();
int failedCalls = metrics.getNumberOfFailedCalls();
int successfulCalls = metrics.getNumberOfSuccessfulCalls();
long notPermittedCalls = metrics.getNumberOfNotPermittedCalls();
int bufferedCalls = metrics.getNumberOfBufferedCalls();
float slowCallRate = metrics.getSlowCallRate();
int slowCalls = metrics.getNumberOfSlowCalls();
int slowFailedCalls = metrics.getNumberOfSlowFailedCalls();
int slowSuccessfulCalls = metrics.getNumberOfSlowSuccessfulCalls(); log.info(prefixName + " state=" + circuitBreaker.getState() + " , metrics[ failureRate=" + failureRate +
", failedCalls=" + failedCalls +
", successCalls=" + successfulCalls +
", notPermittedCalls=" + notPermittedCalls +
", bufferedCalls=" + bufferedCalls +
", \n\tslowCallRate=" + slowCallRate +
", slowCalls=" + slowCalls +
", slowFailedCalls=" + slowFailedCalls +
", slowSuccessfulCalls=" + slowSuccessfulCalls +
" ]"
);
log.info(prefixName + " circuitBreaker tags:{}", circuitBreaker.getTags());
return circuitBreaker.getState().name();
} public static void circuitBreakerEventListener(CircuitBreaker circuitBreaker) {
circuitBreaker.getEventPublisher()
.onSuccess(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onError(event -> {
log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName());
Throwable throwable = event.getThrowable();
if (throwable instanceof TimeoutException) {
// TODO record to slow call
}
})
.onIgnoredError(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onReset(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onStateTransition(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onCallNotPermitted(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onFailureRateExceeded(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()))
.onSlowCallRateExceeded(event -> log.info("---------- CircuitBreakerEvent:{} CircuitBreakerName:{}", event.getEventType(), event.getCircuitBreakerName()));
}

Resilience4jTest测试类

    @Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8080)); private WebTestClient testClient; private CircuitBreakerRegistry circuitBreakerRegistry;private CircuitBreaker circuitBreaker; private CircuitBreaker circuitBreakerWithTags; private CircuitBreakerConfig circuitBreakerConfig;private String PATH_200 = "/api/pancake/v1/yee/query"; private String PATH_400 = "/api/hk/card/v1/er/query"; private String PATH_408 = "/api/pancake/v1/coin/query"; private String PATH_500 = "/api/hk/card/v1/card/query"; @Before
public void setup() {
HttpClient httpClient = HttpClient.create().wiretap(true);
testClient = WebTestClient.bindToServer(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8080")
.responseTimeout(Duration.ofDays(1))
.build(); circuitBreakerRegistry = new InMemoryCircuitBreakerRegistry();
circuitBreakerConfig = CircuitBreakerConfig
.custom()
.failureRateThreshold(70)
.slowCallRateThreshold(90)
.slowCallDurationThreshold(Duration.ofMillis(1000 * 1))
.minimumNumberOfCalls(10)
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10)
.build();
circuitBreaker = circuitBreakerRegistry.circuitBreaker("resilience4jTest", circuitBreakerConfig);
Resilience4jTestHelper.circuitBreakerEventListener(circuitBreaker); stubFor(post(urlMatching(PATH_200))
.willReturn(okJson("{}"))); stubFor(post(urlMatching(PATH_400))
.willReturn(badRequest())); stubFor(post(urlMatching(PATH_408))
.willReturn(okJson("{\"message\":\"time out\"}").withFixedDelay(1000 * 2))); stubFor(post(urlMatching(PATH_500))
.willReturn(serverError()));
} @Test
public void When_Test_CircuitBreaker_Expect_Close() {
AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
Resilience4jTestHelper.recordResponseToCircuitBreaker(circuitBreaker, testClient, PATH_200);
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> end call " + count.incrementAndGet(), circuitBreaker);
}
assertEquals(CircuitBreaker.State.CLOSED.name(), circuitBreaker.getState().name());
} @Test
public void When_CircuitBreaker_Expect_Open() {
circuitBreakerWithTags = circuitBreakerRegistry.circuitBreaker("circuitBreakerWithTags", circuitBreakerConfig, HashMap.of("resilience4jTest", "When_CircuitBreaker_Expect_Open"));
Resilience4jTestHelper.circuitBreakerEventListener(circuitBreakerWithTags); AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
Resilience4jTestHelper.recordResponseToCircuitBreaker(circuitBreakerWithTags, testClient, PATH_400);
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> end call " + count.incrementAndGet(), circuitBreakerWithTags);
}
assertEquals(CircuitBreaker.State.OPEN.name(), circuitBreakerWithTags.getState().name());
} @Test
public void When_Test_CircuitBreaker_Expect_SlowCall() throws Throwable {
AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
circuitBreaker.executeCheckedSupplier(() -> {
Resilience4jTestHelper.recordSlowCallResponseToCircuitBreaker(circuitBreaker, testClient, PATH_408);
return null;
});
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> end call " + count.incrementAndGet(), circuitBreaker);
}
assertEquals(CircuitBreaker.State.OPEN.name(), circuitBreaker.getState().name());
} @Test
public void When_CircuitBreaker_Expect_Fallback() {
AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 20; i++) {
String path = PATH_500;
CheckedFunction0<String> response =
circuitBreaker.decorateCheckedSupplier(() -> Resilience4jTestHelper.responseToCircuitBreaker(circuitBreaker, testClient, path));
Try<String> result = Try.of(response).map(val -> {
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> call success " + count.incrementAndGet(), circuitBreaker);
return val;
}).recover(CallNotPermittedException.class, throwable -> {
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> open CircuitBreaker " + count.incrementAndGet(), circuitBreaker);
return "hit CallNotPermittedException";
}).recover(throwable -> {
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> call fallback " + count.incrementAndGet(), circuitBreaker);
return "hit fallback";
});
log.info(">>>>>>>>>> result:{}", result.get());
if (count.get() > 10) {
assertEquals("hit CallNotPermittedException", result.get());
}
}
}

1.2 Bulkhead

Bulkhead提供了两种隔板模式的实现,可用于限制并发执行的数量

1. 使用信号量  SemaphoreBulkhead
2. 使用有界队列和固定线程池 FixedThreadPoolBulkhead

其中线程池的方式属于资源占用型,在这个不做讨论,如果感兴趣可以去看看官方的样例

1.2.1 BulkheadConfig

BulkheadConfig是Bulkhead的配置类,使用BulkheadConfig配置类,自定义Blukhead配置。配置类BulkheadConfig有以下属性

配置属性 默认值 描述
maxConcurrentCalls 25 隔板允许的最大并行执行量
maxWaitDuration 0 尝试进入饱和的Bulkhead时应阻塞线程的最长时间

1.2.2 BulkheadRegistry

和CircuitBreaker模块一样,BulkheadRegistry提供了一个内存中的实现类InMemoryBulkheadRegistry,可以使用它来管理(创建和获取)Bulkhead实例。

1.2.3 Bulkhead

Bulkhead接口的静态方法和CircuitBreaker方法命名类似,如下下面的decorateCheckedSupplier方法

    static <T> CheckedFunction0<T> decorateCheckedSupplier(Bulkhead bulkhead,
CheckedFunction0<T> supplier) {
return () -> {
bulkhead.acquirePermission();
try {
return supplier.apply();
} finally {
bulkhead.onComplete();
}
};
}

Bulkhead的静态方法,中主要靠bulkhead.acquirePermission()和bulkhead.tryAcquirePermission()申请执行权限,靠bulkhead.onComplete()是释放执行权限,当然还有一个方法bulkhead.releasePermission() 也可以释放执行权限,两者区别就是bulkhead.onComplete()多了一个触发执行完成的事件publishBulkheadEvent(() -> new BulkheadOnCallFinishedEvent(name))。

如果我们不想用Bulkhead自带的静态方法也是可以的,比如我下面的demo, 仅仅使用bulkhead.tryAcquirePermission()和bulkhead.onComplete(),就可以模拟一个服务过载的场景

1.2.4 Bulkhead Demo

Resilience4jTestHelper测试辅助类

    public static String responseToBulkhead(Bulkhead bulkhead, WebTestClient testClient, String path) {
WebTestClient.ResponseSpec responseSpec = testClient.post().uri(path).exchange();
if (bulkhead.getMetrics().getAvailableConcurrentCalls() < 1) {
throw BulkheadFullException.createBulkheadFullException(bulkhead);
}
try {
responseSpec.expectStatus().is4xxClientError();
throw new RuntimeException("<<<<< hit 4XX >>>>>");
} catch (Throwable error) {
} try {
responseSpec.expectStatus().is5xxServerError();
throw new RuntimeException("<<<<< hit 5XX >>>>>");
} catch (Throwable error) {
}
responseSpec.expectStatus().is2xxSuccessful();
return "hit 200";
} /**
* get the Bulkhead status and metrics
* * @param prefixName
*
* @param bulkhead
*/
public static void getBulkheadStatus(String prefixName, Bulkhead bulkhead) {
Bulkhead.Metrics metrics = bulkhead.getMetrics();
int availableCalls = metrics.getAvailableConcurrentCalls();
int maxCalls = metrics.getMaxAllowedConcurrentCalls();
log.info(prefixName + "bulkhead metrics[ availableCalls=" + availableCalls +
", maxCalls=" + maxCalls + " ],tags=" + bulkhead.getTags());
} public static void bulkheadEventListener(Bulkhead bulkhead) {
bulkhead.getEventPublisher()
.onCallRejected(event -> log.info("---------- BulkheadEvent:{} BulkheadName:{}", event.getEventType(), event.getBulkheadName()))
.onCallFinished(event -> log.info("---------- BulkheadEvent:{} BulkheadName:{}", event.getEventType(), event.getBulkheadName()));
} static int[] container = new int[100];
// 模拟一定概率的不释放资源
public static boolean releasePermission() {
if (container[0] != 1) {
for (int i = 0; i < 70; i++) {
container[i] = 1;
}
for (int i = 70; i < 100; i++) {
container[i] = 0;
}
}
int index = (int) (Math.random() * 100);
return container[index] == 1;
}

Resilience4jTest测试类

private BulkheadRegistry bulkheadRegistry;private String PATH_200 = "/api/pancake/v1/yee/query";

    private String PATH_400 = "/api/hk/card/v1/er/query";

    private String PATH_408 = "/api/pancake/v1/coin/query";

    private String PATH_500 = "/api/hk/card/v1/card/query";

    @Before
public void setup() {
HttpClient httpClient = HttpClient.create().wiretap(true);
testClient = WebTestClient.bindToServer(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8080")
.responseTimeout(Duration.ofDays(1))
.build(); bulkheadRegistry = new InMemoryBulkheadRegistry(); stubFor(post(urlMatching(PATH_200))
.willReturn(okJson("{}"))); stubFor(post(urlMatching(PATH_400))
.willReturn(badRequest())); stubFor(post(urlMatching(PATH_408))
.willReturn(okJson("{\"message\":\"time out\"}").withFixedDelay(1000 * 2))); stubFor(post(urlMatching(PATH_500))
.willReturn(serverError()));
} @Test
public void When_Test_CircuitBreaker_With_Bulkhead_Expect_Hit_BulkheadFullException() {
AtomicInteger count = new AtomicInteger();
ExecutorService executorService = Executors.newFixedThreadPool(50);
Bulkhead bulkhead1 = bulkheadRegistry.bulkhead("bulkhead1",
BulkheadConfig
.custom()
.maxConcurrentCalls(20)
.maxWaitDuration(Duration.ofMillis(100))
.build()); Resilience4jTestHelper.bulkheadEventListener(bulkhead1);
for (int i = 0; i < 100; i++) {
if (bulkhead1.tryAcquirePermission()) {
log.info(">>>>>>>>>> acquire permission {}", count.incrementAndGet());
Future<String> futureStr = executorService.submit(() -> Resilience4jTestHelper.responseToBulkhead(bulkhead1, testClient, PATH_200));
Try.of(futureStr::get).andThen(val -> log.info(">>>>>>>>>> success {}: {}", count.get(), val)).recover(throwable -> {
if (throwable instanceof ExecutionException) {
Throwable cause = (ExecutionException) throwable.getCause();
if (cause instanceof BulkheadFullException) {
log.info(">>>>>>>>>> BulkheadFullException {}: {}", count.get(), throwable.getMessage());
} else {
log.info(">>>>>>>>>> ExecutionException {}: {}", count.get(), throwable.getMessage());
}
}
return "hit ExecutionException";
});
if (releasePermission()) {
bulkhead1.onComplete();
log.info("---------- release permission");
}
Resilience4jTestHelper.getBulkheadStatus(")))))))))) ", bulkhead1);
} else {
log.info(">>>>>>>>>> tryAcquirePermission false {}", count.incrementAndGet());
continue;
}
}
executorService.shutdown();
}

1.3 RateLimiter

Resilience4j提供了一个RateLimiter作为限速器,Ratelimiter限制了服务被调用的次数,每隔一段时间重置该次数,服务在超出等待时间之后返回异常或者fallback方法。跟CircuitBreaker的代码结构一样,核心类有RateLimiterRegistry和其实现类InMemoryRateLimiterRegistry,RateLimiterConfig 还有RateLimiter

其中RateLimiterConfig的属性如下表

配置属性 默认值 描述
timeoutDuration 5 [s] 线程等待权限的默认等待时间
limitRefreshPeriod 500 [ns] 限制刷新的周期。在每个周期之后,速率限制器将其权限计数设置回 limitForPeriod 值
limitForPeriod 50 一个限制刷新期间可用的权限数

所以如果你想限制某个方法的调用率不高于1000 req/s,可以做如下配置

RateLimiterConfig.custom()
.timeoutDuration(Duration.ofMillis(1000*5))
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(1000)
.build());

1.3.1 RateLimiter Demo

Resilience4jTestHelper测试辅助类

    /**
* get the RateLimiter status and metrics
* * @param prefixName
*
* @param rateLimiter
*/
public static void getRateLimiterStatus(String prefixName, RateLimiter rateLimiter) {
RateLimiter.Metrics metrics = rateLimiter.getMetrics();
int availablePermissions = metrics.getAvailablePermissions();
int waitingThreads = metrics.getNumberOfWaitingThreads();
log.info(prefixName + "rateLimiter metrics[ availablePermissions=" + availablePermissions +
", waitingThreads=" + waitingThreads + " ]"
);
} public static void rateLimiterEventListener(RateLimiter rateLimiter) {
rateLimiter.getEventPublisher()
.onSuccess(event -> log.info("---------- rateLimiter success:{}", event))
.onFailure(event -> log.info("---------- rateLimiter failure:{}", event));
} public static String responseToRateLimiter(RateLimiter rateLimiter,WebTestClient testClient, String path) {
WebTestClient.ResponseSpec responseSpec = testClient.post().uri(path).exchange();
try {
responseSpec.expectStatus().is4xxClientError();
rateLimiter.onError(new RuntimeException("<<<<< hit 4XX >>>>>"));
throw new RuntimeException("<<<<< hit 4XX >>>>>");
} catch (Throwable error) {
} try {
responseSpec.expectStatus().is5xxServerError();
rateLimiter.onError(new RuntimeException("<<<<< hit 5XX >>>>>"));
throw new RuntimeException("<<<<< hit 5XX >>>>>");
} catch (Throwable error) {
}
responseSpec.expectStatus().is2xxSuccessful();
rateLimiter.onSuccess();
return "hit 200";
}

Resilience4jTest测试类

    private RateLimiterRegistry rateLimiterRegistry;
private RateLimiter rateLimiter; rateLimiterRegistry = new InMemoryRateLimiterRegistry();
rateLimiter = rateLimiterRegistry.rateLimiter("resilience4jTest",
RateLimiterConfig
.custom()
.timeoutDuration(Duration.ofMillis(100))
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(20)
.build());
Resilience4jTestHelper.rateLimiterEventListener(rateLimiter); @Test
public void When_Test_CircuitBreaker_Expect_Hit_RateLimiter() throws Exception {
AtomicInteger count = new AtomicInteger();
ExecutorService executorService = Executors.newFixedThreadPool(50);
String path = expectError() ? PATH_500 : PATH_200;
for (int i = 0; i < 100; i++) {
Future<String> futureStr = executorService.submit(() -> Resilience4jTestHelper.responseToRateLimiter(rateLimiter, testClient, path));
try {
Future<String> stringFuture = rateLimiter.executeCallable(() -> futureStr);
Try.of(stringFuture::get).andThen(val -> {
log.info(">>>>>>>>>> success {}: {}", count.incrementAndGet(), val);
}).recover(throwable -> {
log.info(">>>>>>>>>> exception {}: {}", count.incrementAndGet(), throwable.getMessage());
return "hit fallback";
});
Resilience4jTestHelper.getRateLimiterStatus(")))))))))) ", rateLimiter);
} catch (RequestNotPermitted exception){
assertEquals("RateLimiter 'resilience4jTest' does not permit further calls" , exception.getMessage());
}
}
executorService.shutdown();
}

1.4 Retry

Retry在服务调用返回失败时提供了额外尝试调用的功能,其中RetryConfig的属性如下表

配置属性 默认值 描述
maxAttempts 3 最大尝试次数(包括首次调用作为第一次尝试)
waitDuration 500 [ms] 两次重试的时间间隔
intervalFunction numOfAttempts -> waitDuration 自定义的IntervalFunction,可以根据当前尝试的次数动态的修改重试的时间间隔
intervalBiFunction (numOfAttempts, Either<throwable, result>) -> waitDuration 根据尝试次数和结果或异常修改失败后等待间隔的函数。与 intervalFunction 一起使用时会抛出 IllegalStateException。
retryOnResultPredicate result -> false 自定义的Predicate,根据服务返回的结果判断是否应该重试。如果需要重试Predicate应返回true,否则返回false
retryExceptionPredicate throwable -> true 自定义的Predicate,根据服务返回的异常判断是否应该重试。如果需要重试Predicate应返回true,否则返回false
retryExceptions empty 异常列表,遇到列表中的异常或其子类则重试
注意:如果您使用 Checked Exceptions,则必须使用 CheckedSupplier
ignoreExceptions empty 异常列表,遇到列表中的异常或其子类则不重试。此参数支持子类型。
failAfterMaxRetries false 当重试达到配置的 maxAttempts 并且结果仍未通过 retryOnResultPredicate 时启用或禁用抛出 MaxRetriesExceededException 的布尔值

1.4.1 Retry Demo

Resilience4jTestHelper测试辅助类

    /**
* get the Retry status and metrics
* * @param prefixName
*
* @param retry
*/
public static void getRetryStatus(String prefixName, Retry retry) { Retry.Metrics metrics = retry.getMetrics();
long successfulCallsWithRetryAttempt = metrics.getNumberOfSuccessfulCallsWithRetryAttempt();
long successfulCallsWithoutRetryAttempt = metrics.getNumberOfSuccessfulCallsWithoutRetryAttempt();
long failedCallsWithRetryAttempt = metrics.getNumberOfFailedCallsWithRetryAttempt();
long failedCallsWithoutRetryAttempt = metrics.getNumberOfFailedCallsWithoutRetryAttempt(); log.info(prefixName + " -> retry metrics[ successfulCallsWithRetry=" + successfulCallsWithRetryAttempt +
", successfulCallsWithoutRetry=" + successfulCallsWithoutRetryAttempt +
", failedCallsWithRetry=" + failedCallsWithRetryAttempt +
", failedCallsWithoutRetry=" + failedCallsWithoutRetryAttempt +
" ]"
);
} public static void retryEventListener(Retry retry) {
retry.getEventPublisher()
.onSuccess(event -> log.info("))))))))))) retry service success:{}", event))
.onError(event -> {
log.info("))))))))))) retry service failed:{}", event);
Throwable exception = event.getLastThrowable();
if (exception instanceof TimeoutException) {
// TODO
}
})
.onIgnoredError(event -> log.info("))))))))))) retry service failed and ignore:{}", event))
.onRetry(event -> log.info("))))))))))) retry call service: {}", event.getNumberOfRetryAttempts())); } public static String responseToRetry(Retry retry, WebTestClient testClient, String path) {
WebTestClient.ResponseSpec responseSpec = testClient.post().uri(path).exchange();
try {
responseSpec.expectStatus().is4xxClientError();
return "HIT_ERROR_4XX";
} catch (Throwable error) {
}
try {
responseSpec.expectStatus().is5xxServerError();
return "HIT_ERROR_5XX";
} catch (Throwable error) {
}
responseSpec.expectStatus().is2xxSuccessful();
return "HIT_200";
}

Resilience4jTest测试类

    private RetryRegistry retryRegistry;
private Retry retry;
retryRegistry = new InMemoryRetryRegistry();
retry = retryRegistry.retry("resilience4jTest",
RetryConfig
.custom()
.maxAttempts(5)
.waitDuration(Duration.ofMillis(500))
.retryOnResult(val -> val.toString().contains("HIT_ERROR_"))
// .retryExceptions(RuntimeException.class)
.build()); Resilience4jTestHelper.retryEventListener(retry);
@Test
public void When_Test_CircuitBreaker_Expect_Retry() {
AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 30; i++) {
String path = expectError() ? PATH_200 : PATH_400;
Callable<String> response = Retry.decorateCallable(retry, () -> Resilience4jTestHelper.responseToRetry(retry, testClient, path));
Try.of(response::call).andThen(val -> log.info(">>>>>>>>>> result {}: {}", count.incrementAndGet(), val));
Resilience4jTestHelper.getRetryStatus("))))))))))", retry);
}
}

1.5 TimeLimiter

TimeLImiter超时控制,和CircuitBreaker的slowCall相似,只是CircuitBreaker的slowCall触发了超时只是将超时记录在Metrics中不会抛出异常,而TimeLimiter触发了超时会直接抛出异常。

而且TimeLimiter配置类很简单

配置属性 默认值 描述
timeoutDuration 5 [s] 超时时间,默认1s
cancelRunningFuture TRUE 当触发超时时是否取消运行中的Future

1.5.1 TimeLimiter Demo

Resilience4jTestHelper测试辅助类

    public static void timeLimiterEventListener(TimeLimiter timeLimiter) {
timeLimiter.getEventPublisher()
.onSuccess(event -> log.info("---------- timeLimiter success:{}", event))
.onError(event -> log.info("---------- timeLimiter error:{}", event))
.onTimeout(event -> log.info("---------- rateLimiter timeout:{}", event));
} public static String responseToTimeLimiter(TimeLimiter timeLimiter, CircuitBreaker circuitBreaker, WebTestClient testClient, String path) {
WebTestClient.ResponseSpec responseSpec = testClient.post().uri(path).exchange();
try {
responseSpec.expectStatus().is4xxClientError();
circuitBreaker.onError(0, TimeUnit.MILLISECONDS, new RuntimeException("<<<<< hit 4XX >>>>>"));
timeLimiter.onError(new RuntimeException("<<<<< hit 4XX >>>>>"));
throw new RuntimeException("<<<<< hit 4XX >>>>>");
} catch (Throwable error) {
} try {
responseSpec.expectStatus().is5xxServerError();
circuitBreaker.onError(0, TimeUnit.MILLISECONDS, new RuntimeException("<<<<< hit 5XX >>>>>"));
timeLimiter.onError(new RuntimeException("<<<<< hit 5XX >>>>>"));
throw new RuntimeException("<<<<< hit 5XX >>>>>");
} catch (Throwable error) {
}
responseSpec.expectStatus().is2xxSuccessful();
timeLimiter.onSuccess();
return "hit 200";
}

Resilience4jTest测试类

    private TimeLimiterRegistry timeLimiterRegistry;
private TimeLimiter timeLimiter; timeLimiterRegistry = new InMemoryTimeLimiterRegistry();
timeLimiter = timeLimiterRegistry.timeLimiter("resilience4jTest",
TimeLimiterConfig
.custom()
.timeoutDuration(Duration.ofMillis(1000 * 1))
.cancelRunningFuture(true)
.build()); Resilience4jTestHelper.timeLimiterEventListener(timeLimiter); @Test
public void When_Test_CircuitBreaker_Expect_Timeout() {
AtomicInteger count = new AtomicInteger();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 30; i++) {
String path = expectError() ? PATH_408 : PATH_200;
Future<String> futureStr =
executorService.submit(() -> Resilience4jTestHelper.responseToTimeLimiter(timeLimiter, circuitBreaker, testClient, path));
Callable<String> stringCallable = timeLimiter.decorateFutureSupplier(() -> futureStr);
Callable<String> response = circuitBreaker.decorateCallable(stringCallable);
Try.of(response::call).andThen(val -> log.info(">>>>>>>>>> success {} {}", count.incrementAndGet(), val))
.recover(CallNotPermittedException.class, throwable -> {
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> open CircuitBreaker " + count.incrementAndGet(), circuitBreaker);
return "hit CircuitBreaker";
}).recover(throwable -> {
Resilience4jTestHelper.getCircuitBreakerStatus(">>>>>>>>>> call fallback " + count.incrementAndGet(), circuitBreaker);
log.error(">>>>>>>>>> fallback:{}", throwable.getMessage());
return "hit Fallback";
});
}
}

到此Resilience4j组件的基本用法介绍完毕,上面的测试代码我没有截图测试的结果,附上代码地址各位看官可以在本地跑跑测试代码

代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-gateway/src/test/java/com/kawa/spbgateway/circuitbreaker/resilience4j

springcloud3(六) 服务降级限流熔断组件Resilience4j的更多相关文章

  1. Hystrix介绍以及服务的降级限流熔断

    (dubbo熔断,Hystrix问的少) 无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源.作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang (挂起)在这个资源上, ...

  2. springBoot整合Sentinel实现降级限流熔断

    由于hystrix的停止更新,以及阿里Sentinel在历年双十一的贡献.项目中使用了Sentinel,今天我们来讲讲Sentinel的入门教程,本文使用1.6.3版本进行讲解 本文通过Sentine ...

  3. .Net微服务实践(四)[网关]:Ocelot限流熔断、缓存以及负载均衡

    目录 限流 熔断 缓存 Header转化 HTTP方法转换 负载均衡 注入/重写中间件 后台管理 最后 在上篇.Net微服务实践(三)[网关]:Ocelot配置路由和请求聚合中我们介绍了Ocelot的 ...

  4. .Net Core的API网关Ocelot的使用(二)[负载,限流,熔断,Header转换]

    网关的负载均衡 当下游拥有多个节点的时候,我们可以用DownstreamHostAndPorts来配置 { "UpstreamPathTemplate": "/Api_A ...

  5. .Net Core使用Ocelot网关(一) -负载,限流,熔断,Header转换

    1.什么是API网关 API网关是微服务架构中的唯一入口,它提供一个单独且统一的API入口用于访问内部一个或多个API.它可以具有身份验证,监控,负载均衡,缓存,请求分片与管理,静态响应处理等.API ...

  6. .net core使用ocelot---第四篇 限流熔断

    简介 .net core使用ocelot---第一篇 简单使用 .net core使用ocelot---第二篇 身份验证 .net core使用ocelot---第三篇 日志记录 前几篇文章我们陆续介 ...

  7. 前后端分离djangorestframework——限流频率组件

    频率限制 什么是频率限制 目前我们开发的都是API接口,且是开房的API接口.传给前端来处理的,也就是说,只要有人拿到这个接口,任何人都可以通过这个API接口获取数据,那么像网络爬虫的,请求速度又快, ...

  8. DBPack 限流熔断功能发布说明

    上周我们发布了 v0.4.0 版本,增加了限流熔断功能,现对这两个功能做如下说明. 限流 DBPack 限流熔断功能通过 filter 实现.要设置限流规则,首先要定义 RateLimitFilter ...

  9. 微服务容错限流Hystrix入门

    为什么需要容错限流 复杂分布式系统通常有很多依赖,如果一个应用不能对来自依赖 故障进行隔离,那么应用本身就处在被拖垮的风险中.在一个高流量的网站中,某个单一后端一旦发生延迟,将会在数秒内导致 所有应用 ...

随机推荐

  1. C# Redis学习系列二:Redis基本设置

    上一篇:C# Redis学习系列一:Redis的认识.下载.安装.使用 一.redis 设置密码 使用下载好的 redis-cli.exe 指令: 1.设置密码: config set require ...

  2. 基于pgpool搭建postgressql集群部署

    postgresql集群搭建 基于pgpool中间件实现postgresql一主多从集群部署,这里用两台服务器作一主一从示例 虚拟机名 IP 主从划分 THApps 192.168.1.31 主节点 ...

  3. web 阶段的一些简答题

    1.jsp 9个隐含对象 2. jsp 4大域对象 3.mybatis 中 #{} %{ } 的区别于联系 4. Servlet容器默认是采用单实例多线程的方式处理多个请求的: 5.Cookie 与S ...

  4. P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】

    前言 话说在\(Loj\)下了个数据发现这题的名字叫\(fgo\) 正题 题目链接:https://www.luogu.com.cn/problem/P5405 题目大意 \(n\)张卡的权值为\(1 ...

  5. Linkerd stable-2.11.0 稳定版发布:授权策略、gRPC 重试、性能改进等!

    公众号:黑客下午茶 授权策略 Linkerd 的新服务器授权策略(server authorization policy)功能使您可以细粒度控制允许哪些服务相互通信.这些策略直接建立在 Linkerd ...

  6. Cookie实现是否第一次登陆/显示上次登陆时间

    Cookie实现是否第一次登陆/显示上次登陆时间 最近刚好看到Cookie这方面知识,对Servlet部分知识已经生疏,重新翻出已经遗弃角落的<JavaWeb开发实战经典>,重新温习了Co ...

  7. JAVA 150道笔试题知识点整理

    JAVA 笔试题 整理了几天才整理的题目,都是在笔试或者面试碰到的,好好理解消化下,对你会有帮助,祝你找工作顺利,收到满意的 offer . 1.Java 基础知识 1.1 Java SE 语法 &a ...

  8. NOIP模拟69

    T1 石子游戏 大坑未补 T2 大鱼吃小鱼 解题思路 set+桶可以得到 60pts (code) 线段树上二分每一次优先递归右区间从右区间贪心选择,并且记录下更改过的值,在处理完答案之后再复原回去. ...

  9. Knativa 基于流量的灰度发布和自动弹性实践

    作者 | 李鹏(元毅) 来源 | Serverless 公众号 一.Knative Knative 提供了基于流量的自动扩缩容能力,可以根据应用的请求量,在高峰时自动扩容实例数:当请求量减少以后,自动 ...

  10. SpringBoot入门03-转发到Thymeleaf

    前言 Spring Boot不提倡使用jsp和用View层,而是使用Thymeleaf代替jsp,因为性能可以得到提升. 使用Thymeleaf要加入依赖 Thymeleaf不能直接被访问,它严格遵守 ...