This is the 14th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

This series code address: github.com/JoJoTec/spr…

Let’s continue testing our retries in the previous section

Verify that the retry is correct for the current limiter exception

From the previous source code analysis in this series, we know that Spring-Cloud-OpenFeign’s FeignClient is lazily loaded. So the circuit breaker we implemented is lazily loaded and needs to be called first before thread isolation is initialized. Therefore, if we want to simulate the exception of full thread isolation, we need to manually read the load thread isolation, and then obtain the corresponding instance of thread isolation, and fill the thread pool.

Let’s define a FeignClient:

@FeignClient(name = "testService1", contextId = "testService1Client") public interface TestService1Client { @GetMapping("/anything") HttpBinAnythingResponse  anything(); }Copy the code

Add an instance to the microservice in the same way as before:

//SpringExtension also includes Mockito related Extension, So @mock annotations like @extendWith (springExtension.class) @springbooTtest (properties = {// Close eureka Client "Eureka. Client. Enabled = false", / / the default request retry count to three "resilience4j. Retry. Configs. Default. MaxAttempts = 3", / / increase circuit breaker configuration "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 () {// Simulate two service instances. 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"); / / this is httpbin.org, in order to and can be distinguished in the first instance and the WWW when (service1Instance3. GetHost ()). ThenReturn (" www.httpbin.org "); DiscoveryClient spy = Mockito.spy(DiscoveryClient.class); TestService3 mockito. when(spy.getInstances("testService1")) .thenReturn(List.of(service1Instance1, service1Instance3)); return spy; }}}Copy the code

Then, write the test code:

@ Test public void testRetryOnBulkheadException () {/ / prevent the circuit breaker circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); this.testService1Client.anything(); ThreadPoolBulkhead threadPoolBulkhead; try { threadPoolBulkhead = threadPoolBulkheadRegistry .bulkhead("testService1Client:httpbin.org:80", "testService1Client"); } the catch (ConfigurationNotFoundException e) {/ / threadPoolBulkhead = threadPoolBulkheadRegistry couldn't find it with the default configuration .bulkhead("testService1Client:httpbin.org:80"); } for (int I = 0; int I = 0; i < 10 + 1; I++) {threadPoolBulkhead. Submit (() - > {try {/ / never end this mission by Thread. The currentThread (). The join (); } catch (InterruptedException e) { e.printStackTrace(); }}); } for (int I = 0; i < 10; i++) { this.testService1Client.anything(); }}Copy the code

Run the test and you can see from the log that the thread pool is full exception was retried:

The 2021-11-13 03:35:16. 371 INFO [and] 3824 - [the main] C.G.J.S.C.W.F.D efaultErrorDecoder: TestService1Client#anything() response: 584-Bulkhead 'testService1Client:httpbin.org:80' is full and does not permit further calls, should retry: trueCopy the code

Verify that retried methods for non-2XX response codes are correctly retried

Bin’s /status/{statusCode} interface returns a status code response based on the path parameter statusCode:

@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {
    @GetMapping("/status/500")
    String testGetRetryStatus500();
}
Copy the code

How do we sense being retried three times? Each invocation gets a service instance from the load balancer. In the load balancer code, we use a cache of traceId based on the current sleUTH context, with the position value of the traceId incrementing by one on each call. We can see how many times the load balancer was called, or called, by watching this value change.

Writing tests:

@Test public void testNon2xxRetry() { Span span = tracer.nextSpan(); Try (tracer.spaninscope cleared = tracer.withSPANinscope (span)) {// Prevent the impact of circuit breakers 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 methods will retry testService1Client testGetRetryStatus500 (); } catch (Exception e) {} // Because each call fails, configured 3 times are retried. AssertEquals (3, atomicInteer.get () -start); }}Copy the code

Verify that methods that are not retried for non-2XX response codes are not retried

Bin /status/{statusCode} interface, which returns a status code response based on the path parameter statusCode and supports various HTTP requests:

@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {
    @PostMapping("/status/500")
    String testPostRetryStatus500();
}
Copy the code

By default, we only retry GET methods, not other HTTP request methods:

@Test public void testNon2xxRetry() { Span span = tracer.nextSpan(); Try (tracer.spaninscope cleared = tracer.withSPANinscope (span)) {// Prevent the impact of circuit breakers 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 method will not retry testService1Client testPostRetryStatus500 (); } Catch (Exception e) {} // Will not be retried, so is only called 1 time.asserteQuals (1, atomicInteer.get () -start); }}Copy the code

Wechat search “my programming meow” public account, a daily brush, easy to improve skills, won a variety of offers: