这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战
本系列代码地址:github.com/JoJoTec/spr…
在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器、重试以及线程隔离,并使用了新的负载均衡算法优化了业务激增时的负载均衡算法表现。这一节,我们开始编写单元测试验证这些功能的正确性,以便于日后升级依赖,修改的时候能保证正确性。同时,通过单元测试,我们更能深入理解 Spring Cloud。
验证重试配置
对于我们实现的重试,我们需要验证:
- 验证配置正确加载:即我们在 Spring 配置(例如
application.yml
)中的加入的 Resilience4j 的配置被正确加载应用了。 - 验证针对 ConnectTimeout 重试正确:FeignClient 可以配置 ConnectTimeout 连接超时时间,如果连接超时会有连接超时异常抛出,对于这种异常无论什么请求都应该重试,因为请求并没有发出。
- 验证针对断路器异常的重试正确:断路器是微服务实例方法级别的,如果抛出断路器打开异常,应该直接重试下一个实例。
- 验证针对限流器异常的重试正确:当某个实例线程隔离满了的时候,抛出线程限流异常应该直接重试下一个实例。
- 验证针对非 2xx 响应码可重试的方法重试正确
- 验证针对非 2xx 响应码不可重试的方法没有重试
- 验证针对可重试的方法响应超时异常重试正确:FeignClient 可以配置 ReadTimeout 即响应超时,如果方法可以重试,则需要重试。
- 验证针对不可重试的方法响应超时异常不能重试:FeignClient 可以配置 ReadTimeout 即响应超时,如果方法不可以重试,则不能重试。
验证配置正确加载
我们可以定义不同的 FeignClient,之后检查 resilience4j 加载的重试配置来验证重试配置的正确加载。
首先定义两个 FeignClient,微服务分别是 testService1 和 testService2,contextId 分别是 testService1Client 和 testService2Client
1 | less复制代码@FeignClient(name = "testService1", contextId = "testService1Client") |
然后,我们增加 Spring 配置,使用 SpringExtension 编写单元测试类:
1 | less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了 |
编写测试代码,验证配置加载正确性:
1 | scss复制代码@Test |
验证针对 ConnectTimeout 重试正确
我们可以通过针对一个微服务注册两个实例,一个实例是连接不上的,另一个实例是可以正常连接的,无论怎么调用 FeignClient,请求都不会失败,来验证重试是否生效。我们使用 HTTP 测试网站来测试,即 httpbin.org 。这个网站的 api 可以用来模拟各种调用。其中 /status/{status}
就是将发送的请求原封不动的在响应中返回。在单元测试中,我们不会单独部署一个注册中心,而是直接 Mock spring cloud 中服务发现的核心接口 DiscoveryClient,并且将我们 Eureka 的服务发现以及注册通过配置都关闭,即:
1 | less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了 |
编写 FeignClient:
1 | kotlin复制代码@FeignClient(name = "testService3", contextId = "testService3Client") |
调用 TestService3Client 的 anything
方法,验证是否有重试:
1 | scss复制代码@SpyBean |
这里强调一点,由于我们在这个类中还会测试其他异常,以及断路器,我们需要避免这些测试一起执行的时候,断路器打开了,所以我们在所有测试调用 FeignClient 的方法开头,清空所有断路器的数据,通过:
1 | scss复制代码circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); |
并且通过日志中可以看出由于 connect timeout 进行重试:
1 | ruby复制代码call url: POST -> http://www.httpbin.org:18080/anything, ThreadPoolStats(testService3Client:www.httpbin.org:18080): {"coreThreadPoolSize":10,"maximumThreadPoolSize":10,"queueCapacity":100,"queueDepth":0,"remainingQueueCapacity":100,"threadPoolSize":1}, CircuitBreakStats(testService3Client:www.httpbin.org:18080:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService3Client.anything()): {"failureRate":-1.0,"numberOfBufferedCalls":0,"numberOfFailedCalls":0,"numberOfNotPermittedCalls":0,"numberOfSlowCalls":0,"numberOfSlowFailedCalls":0,"numberOfSlowSuccessfulCalls":0,"numberOfSuccessfulCalls":0,"slowCallRate":-1.0} |
验证针对断路器异常的重试正确
通过系列前面的源码分析,我们知道 spring-cloud-openfeign 的 FeignClient 其实是懒加载的。所以我们实现的断路器也是懒加载的,需要先调用,之后才会初始化断路器。所以这里如果我们要模拟断路器打开的异常,需要先手动读取载入断路器,之后才能获取对应方法的断路器,修改状态。
我们先定义一个 FeignClient:
1 | kotlin复制代码@FeignClient(name = "testService1", contextId = "testService1Client") |
使用前面同样的方式,给这个微服务添加实例:
1 | less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了 |
然后,编写测试代码:
1 | csharp复制代码@Test |
运行测试,日志中可以看出,针对断路器打开的异常进行重试了:
1 | yaml复制代码2021-11-13 03:40:13.546 INFO [,,] 4388 --- [ main] c.g.j.s.c.w.f.DefaultErrorDecoder : TestService1Client#anything() response: 581-CircuitBreaker 'testService1Client:httpbin.org:80:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService1Client.anything()' is OPEN and does not permit further calls, should retry: true |
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer
本文转载自: 掘金