共计 5130 个字符,预计需要花费 13 分钟才能阅读完成。
本系列代码地址:https://github.com/JoJoTec/sp…
咱们持续上一节针对咱们的重试进行测试
验证针对限流器异样的重试正确
通过系列后面的源码剖析,咱们晓得 spring-cloud-openfeign 的 FeignClient 其实是懒加载的。所以咱们实现的断路器也是懒加载的,须要先调用,之后才会初始化线程隔离。所以这里如果咱们要模仿线程隔离满的异样,须要先手动读取载入线程隔离,之后能力获取对应实例的线程隔离,将线程池填充斥。
咱们先定义一个 FeignClient:
@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {@GetMapping("/anything")
HttpBinAnythingResponse anything();}
应用后面同样的形式,给这个微服务增加实例:
//SpringExtension 也蕴含了 Mockito 相干的 Extension,所以 @Mock 等注解也失效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
// 敞开 eureka client
"eureka.client.enabled=false",
// 默认申请重试次数为 3
"resilience4j.retry.configs.default.maxAttempts=3",
// 减少断路器配置
"resilience4j.circuitbreaker.configs.default.failureRateThreshold=50",
"resilience4j.circuitbreaker.configs.default.slidingWindowType=COUNT_BASED",
"resilience4j.circuitbreaker.configs.default.slidingWindowSize=5",
"resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=2",
})
@Log4j2
public class OpenFeignClientTest {
@SpringBootApplication
@Configuration
public static class App {
@Bean
public DiscoveryClient discoveryClient() {
// 模仿两个服务实例
ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class);
ServiceInstance service1Instance3 = 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("httpbin.org");
when(service1Instance1.getPort()).thenReturn(80);
when(service1Instance3.getMetadata()).thenReturn(zone1);
when(service1Instance3.getInstanceId()).thenReturn("service1Instance3");
// 这其实就是 httpbin.org,为了和第一个实例进行辨别加上 www
when(service1Instance3.getHost()).thenReturn("www.httpbin.org");
DiscoveryClient spy = Mockito.spy(DiscoveryClient.class);
// 微服务 testService3 有两个实例即 service1Instance1 和 service1Instance4
Mockito.when(spy.getInstances("testService1"))
.thenReturn(List.of(service1Instance1, service1Instance3));
return spy;
}
}
}
而后,编写测试代码:
@Test
public void testRetryOnBulkheadException() {
// 避免断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
this.testService1Client.anything();
ThreadPoolBulkhead threadPoolBulkhead;
try {
threadPoolBulkhead = threadPoolBulkheadRegistry
.bulkhead("testService1Client:httpbin.org:80", "testService1Client");
} catch (ConfigurationNotFoundException e) {
// 找不到就用默认配置
threadPoolBulkhead = threadPoolBulkheadRegistry
.bulkhead("testService1Client:httpbin.org:80");
}
// 线程队列咱们配置的是 1,线程池大小是 10,这样会将线程池填充斥
for (int i = 0; i < 10 + 1; i++) {threadPoolBulkhead.submit(() -> {
try {
// 这样工作永远不会完结了
Thread.currentThread().join();
}
catch (InterruptedException e) {e.printStackTrace();
}
});
}
// 调用屡次,调用胜利即对断路器异样重试了
for (int i = 0; i < 10; i++) {this.testService1Client.anything();
}
}
运行测试,日志中能够看出,针对线程池满的异样进行重试了:
2021-11-13 03:35:16.371 INFO [,,] 3824 --- [main] c.g.j.s.c.w.f.DefaultErrorDecoder : TestService1Client#anything() response: 584-Bulkhead 'testService1Client:httpbin.org:80' is full and does not permit further calls, should retry: true
验证针对非 2xx 响应码可重试的办法重试正确
咱们通过应用 http.bin 的 /status/{statusCode}
接口,这个接口会依据门路参数 statusCode
返回对应状态码的响应:
@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {@GetMapping("/status/500")
String testGetRetryStatus500();}
咱们如何感知被重试三次呢?每次调用,就会从负载均衡器获取一个服务实例。在负载均衡器代码中,咱们应用了依据以后 sleuth 的上下文的 traceId 的缓存,每次调用,traceId 对应的 position 值就会加 1。咱们能够通过观察这个值的变动获取到到底本次申请调用了几次负载均衡器,也就是做了几次调用。
编写测试:
@Test
public void testNon2xxRetry() {Span span = tracer.nextSpan();
try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) {
// 避免断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
long l = span.context().traceId();
RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance
= (RoundRobinWithRequestSeparatedPositionLoadBalancer) loadBalancerClientFactory.getInstance("testService1");
AtomicInteger atomicInteger = loadBalancerClientFactoryInstance.getPositionCache().get(l);
int start = atomicInteger.get();
try {
//get 办法会重试
testService1Client.testGetRetryStatus500();} catch (Exception e) { }
// 因为每次调用都会失败,所以会重试配置的 3 次
Assertions.assertEquals(3, atomicInteger.get() - start);
}
}
验证针对非 2xx 响应码不可重试的办法没有重试
咱们通过应用 http.bin 的 /status/{statusCode}
接口,这个接口会依据门路参数 statusCode
返回对应状态码的响应,并且反对各种 HTTP 申请形式:
@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {@PostMapping("/status/500")
String testPostRetryStatus500();}
默认状况下,咱们只会对 GET 办法重试,对于其余 HTTP 申请办法,是不会重试的:
@Test
public void testNon2xxRetry() {Span span = tracer.nextSpan();
try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) {
// 避免断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
long l = span.context().traceId();
RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance
= (RoundRobinWithRequestSeparatedPositionLoadBalancer) loadBalancerClientFactory.getInstance("testService1");
AtomicInteger atomicInteger = loadBalancerClientFactoryInstance.getPositionCache().get(l);
int start = atomicInteger.get();
try {
//post 办法不会重试
testService1Client.testPostRetryStatus500();} catch (Exception e) { }
// 不会重试,因而只会被调用 1 次
Assertions.assertEquals(1, atomicInteger.get() - start);
}
}
微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种 offer: