资讯专栏INFORMATION COLUMN

SpringCloud升级之路2020.0.x版-36. 验证断路器正确性

NotFound / 3341人阅读

摘要:本系列代码地址上一节我们通过单元测试验证了线程隔离的正确性,这一节我们来验证我们断路器的正确性,主要包括验证配置正确加载即我们在配置例如中的加入的的配置被正确加载应用了。

本系列代码地址: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",        })@Log4j2public 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 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 circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava();        Set 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),验证断路器状态:

@Testpublic 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 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123768.html

相关文章

  • SpringCloud升级之路2020.0.x-40. spock 单元测试封装的 WebClie

    摘要:在上面打开一个微服务某个实例的一个路径的断路器之后,我们调用其他的路径,无论多少次,都成功并且调用负载均衡器获取服务实例的次数等于调用次数,代表没有重试,也就是没有断路器异常。 本系列代码地址:​​https://github.com/JoJoTec/spring-cloud-parent​​我们来测试下前面封装好的 We...

    番茄西红柿 评论0 收藏2637
  • SpringCloud升级之路2020.0.x-40. spock 单元测试封装的 WebClie

    摘要:本系列代码地址我们继续上一节,继续使用测试我们自己封装的测试针对重试测试针对重试针对响应超时,我们需要验证重试仅针对可以重试的方法包括方法以及配置的可重试方法,针对不可重试的方法没有重试。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent我们继续上一节,继续使用 spock 测试我们自己封装的 WebClient测试针对 readTi...

    番茄西红柿 评论0 收藏2637
  • SpringCloud升级之路2020.0.x-37. 实现异步的客户端封装配置管理的意义与设计

    摘要:对于异步的请求,使用的是异步客户端即。要实现的配置设计以及使用举例要实现的配置设计以及使用举例首先,我们要实现的,其包含三个重试重试的要在负载均衡之前,因为重试的时候,我们会从负载均衡器获取另一个实例进行重试,而不是在同一个实例上重试多次。 本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 为何需要封装异步 HT...

    fxp 评论0 收藏0
  • SpringCloud升级之路2020.0.x-41. SpringCloudGateway 基本

    摘要:将请求封装成将请求封装成的接口定义是但是最外层传进来的参数是和,需要将他们封装成,这个工作就是在中做的。其实主要任务就是将各种参数封装成除了和本次请求相关的和,还有会话管理器,编码解码器配置,国际化配置还有用于扩展。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent接下来,将进入我们升级之路的又一大模块,即网关模块。网关模块我们废弃了...

    不知名网友 评论0 收藏0
  • SpringCloud升级之路2020.0.x-41. SpringCloudGateway 基本

    摘要:在这里,会将上下文中载入的拼接成,然后调用其方法的,它是的处理请求业务的起点。添加相关依赖之后,会有这个。路由权重相关配置功能相关实现类,这个我们这里不关心。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent我们继续分析上一节提到的 WebHandler,经过将请求封装成 ServerWebExchange 的 HttpWebHand...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<