本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

上一节我们通过单元测试验证了线程隔离的正确性,这一节我们来验证我们断路器的正确性,主要包括:

  1. 验证配置正确加载:即我们在 Spring 配置(例如 application.yml)中的加入的 Resilience4j 的配置被正确加载应用了。
  2. 验证断路器是基于服务和方法打开的,也就是某个微服务的某个方法断路器打开但是不会影响这个微服务的其他方法调用

验证配置正确加载

与之前验证重试类似,我们可以定义不同的 FeignClient,之后检查 resilience4j 加载的断路器配置来验证线程隔离配置的正确加载。

并且,与重试配置不同的是,通过系列前面的源码分析,我们知道 spring-cloud-openfeign 的 FeignClient 其实是懒加载的。所以我们实现的断路器也是懒加载的,需要先调用,之后才会初始化断路器。所以这里我们需要先进行调用之后,再验证断路器配置。

首先定义两个 FeignClient,微服务分别是 testService1 和 testService2,contextId 分别是 testService1Client 和 testService2Client

@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {
@GetMapping("/anything")
HttpBinAnythingResponse anything();
}
@FeignClient(name = "testService2", contextId = "testService2Client")
public interface TestService2Client {
@GetMapping("/anything")
HttpBinAnythingResponse anything();
}

然后,我们增加 Spring 配置,并且给两个微服务都添加一个实例,使用 SpringExtension 编写单元测试类:

//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
//默认请求重试次数为 3
"resilience4j.retry.configs.default.maxAttempts=3",
// testService2Client 里面的所有方法请求重试次数为 2
"resilience4j.retry.configs.testService2Client.maxAttempts=2",
//默认断路器配置
"resilience4j.circuitbreaker.configs.default.slidingWindowSize=5",
"resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=2",
//testService2Client 的 断路器配置
"resilience4j.circuitbreaker.configs.testService2Client.failureRateThreshold=30",
"resilience4j.circuitbreaker.configs.testService2Client.minimumNumberOfCalls=10", })
@Log4j2
public class OpenFeignClientTest {
@SpringBootApplication
@Configuration
public static class App {
@Bean
public DiscoveryClient discoveryClient() {
//模拟两个服务实例
ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class);
ServiceInstance service2Instance2 = Mockito.spy(ServiceInstance.class);
Map<String, String> zone1 = Map.ofEntries(
Map.entry("zone", "zone1")
);
when(service1Instance1.getMetadata()).thenReturn(zone1);
when(service1Instance1.getInstanceId()).thenReturn("service1Instance1");
when(service1Instance1.getHost()).thenReturn("www.httpbin.org");
when(service1Instance1.getPort()).thenReturn(80);
when(service2Instance2.getInstanceId()).thenReturn("service1Instance2");
when(service2Instance2.getHost()).thenReturn("httpbin.org");
when(service2Instance2.getPort()).thenReturn(80);
DiscoveryClient spy = Mockito.spy(DiscoveryClient.class);
Mockito.when(spy.getInstances("testService1"))
.thenReturn(List.of(service1Instance1));
Mockito.when(spy.getInstances("testService2"))
.thenReturn(List.of(service2Instance2));
return spy;
}
}
}

编写测试代码,验证配置正确:

@Test
public void testConfigureCircuitBreaker() {
//防止断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
//调用下这两个 FeignClient 确保对应的 NamedContext 被初始化
testService1Client.anything();
testService2Client.anything();
//验证断路器的实际配置,符合我们的填入的配置
List<CircuitBreaker> circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava();
Set<String> collect = circuitBreakers.stream().map(CircuitBreaker::getName)
.filter(name -> {
try {
return name.contains(TestService1Client.class.getMethod("anything").toGenericString())
|| name.contains(TestService2Client.class.getMethod("anything").toGenericString());
} catch (NoSuchMethodException e) {
return false;
}
}).collect(Collectors.toSet());
Assertions.assertEquals(collect.size(), 2);
circuitBreakers.forEach(circuitBreaker -> {
if (circuitBreaker.getName().contains(TestService1Client.class.getName())) {
Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) DEFAULT_FAILURE_RATE_THRESHOLD);
Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), DEFAULT_MINIMUM_NUMBER_OF_CALLS);
} else if (circuitBreaker.getName().contains(TestService2Client.class.getName())) {
Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) TEST_SERVICE_2_FAILURE_RATE_THRESHOLD);
Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), TEST_SERVICE_2_MINIMUM_NUMBER_OF_CALLS);
}
});
}

验证断路器是基于服务和方法打开的。

我们给 TestService1Client 添加一个方法:

@GetMapping("/status/500")
String testCircuitBreakerStatus500();

这个方法一定会调用失败,从而导致断路器打开。经过 2 次失败以上后(因为配置最少触发断路器打开的请求个数为 2),验证断路器状态:

@Test
public void testCircuitBreakerOpenBasedOnServiceAndMethod() {
//防止断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
AtomicBoolean passed = new AtomicBoolean(false);
for (int i = 0; i < 10; i++) {
//多次调用会导致断路器打开
try {
System.out.println(testService1Client.testCircuitBreakerStatus500());
} catch(Exception e) {}
List<CircuitBreaker> circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava();
circuitBreakers.stream().filter(circuitBreaker -> {
return circuitBreaker.getName().contains("testCircuitBreakerStatus500")
&& circuitBreaker.getName().contains("TestService1Client");
}).findFirst().ifPresent(circuitBreaker -> {
//验证对应微服务和方法的断路器被打开
if (circuitBreaker.getState().equals(CircuitBreaker.State.OPEN)) {
passed.set(true);
//断路器打开后,调用其他方法,不会抛出断路器打开异常
testService1Client.testAnything();
}
});
} Assertions.assertTrue(passed.get());
}

这样,我们就成功验证了,验证断路器是基于服务和方法打开的。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-36. 验证断路器正确性的更多相关文章

  1. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

  2. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(2)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续上一节针对我们的重试进行测试 验证针对限流器异常的重试正确 通过系列前面的源码分析 ...

  3. SpringCloud升级之路2020.0.x版-35. 验证线程隔离正确性

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 上一节我们通过单元测试验证了重试的正确性,这一节我们来验证我们线程隔离的正确性,主要包括: ...

  4. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(3)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续上一节针对我们的重试进行测试 验证针对可重试的方法响应超时异常重试正确 我们可以通 ...

  5. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  6. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  7. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  8. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  9. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

随机推荐

  1. 1. JVM核心类加载器及类加载的全过程

    运行环境: 下面说明一下我的运行环境.我是在mac上操作的. 先找到mac的java地址. 从~/.bash_profile中可以看到 java的home目录是: /Library/Java/Java ...

  2. Redis分布式方案:集群

    Redis集群通过分片(sharding)进行数据共享,并提供复制和故障转移功能. 节点 一个Redis集群由多个node组成,连接各节点的命令格式如下: CLUSTER MEET 127.0.0.1 ...

  3. HTML对溢出的控制:overflow

    用于对超出模块的内容进行控制 overflow的三种参数 1.visible(默认):溢出部分可见 2.hidden:溢出部分隐藏 3.auto:适当加入滚动条 overflow使用方法 1.常用 o ...

  4. 聊聊我对 GraphQL 的一些认知

    每隔一段时间就能看到一篇 GraphQL 的文章,但是打开文章一看,基本上就是简单的介绍下 GraphQL 的特性.很多文章其实就是 github 上找个 GraphQL 的项目,然后按照对应的 de ...

  5. openGauss X ShardingSphere,分布式方案的另一种最佳实践

    Apache ShardingSphere 持续助力于 openGauss 分布式数据库能力的构建.openGauss 数据库自 2020 年 6 月开源以来,受到了业界的广泛关注,现已吸引众多伙伴. ...

  6. ArrayList-源码分析-自动扩容机制

    ArrayList类: public class ArrayList....{ ...... private static final int DEFAULT_CAPACITY = 10; //默认容 ...

  7. Visual Studio 重置窗口布局

    Visual Studio 重置窗口布局

  8. dubbo注册中心占位符无法解析问题(二)

    dubbo注册中心占位符无法解析问题 前面分析了dubbo注册中心占位符无法解析的问题. 并给出了2种解决办法: 降低mybatis-spring的版本至2.0.1及以下 自定义MapperScann ...

  9. 【MySQL】MySQL(四)存储引擎、索引、锁、集群

    MySQL存储引擎 MySQL体系结构 体系结构的概念 任何一套系统当中,每个部件都能起到一定的作用! MySQL的体系结构 体系结构详解 客户端连接 支持接口:支持的客户端连接,例如C.Java.P ...

  10. Less-(38~41) 堆叠注入

    首先申明,Less-(38~41)可以采取和Less-(1~4)相同的解法:(一一对应) 然而,他们的漏洞其实更大,我们可以做更多具有破坏性的事情. 代码审计: Less-(38~41): 41的$s ...