This is the 9th 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…

Scenarios requiring retry

In microservice system, online publishing will be encountered, the general publishing update strategy is: start a new one, after successful startup, close the old one, until all the old ones are closed. Spring Boot has a graceful shutdown feature that ensures that the request is closed after it has been processed and that new requests are rejected. These rejected requests need to be retried to ensure that the user experience is not affected.

Microservices deployed on the cloud may not all instances of the same service and request be abnormal at the same time. For example:

  1. Kubernetes cluster deployment instances may have multiple microservice instances deployed on the same VIRTUAL machine Node in idle time. When the pressure becomes heavy, migration and capacity expansion are required. At this time, because different microservices have different pressures, which Node is in at that time may not be known, some may be under high pressure, some may be under low pressure. For the same microservice, not all the nodes where the instance resides may be stressed.
  2. On-cloud deployments are typically deployed across availability zones, and if one availability zone fails, the other availability zone can continue to provide services.
  3. A business triggers a Bug that causes instances to remain in GC, but such requests are generally uncommon and not sent to all instances.

At this point, we need to retry the request without awareness.

Retry issues that need to be considered

  1. Retry Retry different instances, or even instances that are not in the same VM Node. This is done primarily through LoadBalancer, as described in the LoadBalancer section above. In a later article, we will also improve LoadBalancer
  2. Retries need to consider what requests can be retried and what exceptions can be retried:
  • If we have a query interface, and do not do idempotent deduction interface, then it is very intuitive to feel that the query interface can be retry, do not do idempotent deduction interface is not retry.
  • An interface that cannot be retried in business. For special exceptions (exceptions that indicate that the request was not sent), we can retry. If an IOException is thrown because of a Connect Timeout, the request has not been sent yet and can be retried.
  1. Retry policy: Retry several times, retry interval. Analogies to the Busy Spin strategy in multiprocessor programming patterns, which can cause high bus throughput and degrade performance, are that if a failure is retried immediately, many requests will be retried to other instances at the same time if an exception in one instance causes a timeout. It is better to add some delay.

Implement FeignClient retry using Resilience4J

FeignClient itself comes with retry, but the retry policy is relatively simple, and we also want to use circuit breakers and current limiters and thread isolation, which Resilience4J includes.

Introduction of the principle

Resilience4J provides Retryer retry, official documents address: Resilience4J. Readme. IO/docs/retry

You can understand the principle from the configuration, but the official configuration documentation is not comprehensive, if you want to see all the configuration, it is best to use the source code:

RetryConfigurationProperties.java

// Retry interval, default 500ms @nullable private Duration waitDuration; WaitDuration @nullable private Class<? WaitDuration @nullable private Class<? extends IntervalBiFunction<Object>> intervalBiFunction; // Maximum number of retries, including the call itself @nullable private Integer maxAttempts; @nullable private Class<? extends Predicate<Throwable>> retryExceptionPredicate; @nullable private Class<? extends Predicate<Object>> resultPredicate; @SuppressWarnings("unchecked") @Nullable private Class<? extends Throwable>[] retryExceptions; @SuppressWarnings("unchecked") @Nullable private Class<? extends Throwable>[] ignoreExceptions; / / enabled ExponentialBackoff delay algorithm, initial retry delay time for waitDuration, after each retry delay time multiplied by exponentialBackoffMultiplier, Until exponentialMaxWaitDuration @ Nullable private Boolean enableExponentialBackoff; private Double exponentialBackoffMultiplier; private Duration exponentialMaxWaitDuration; // Enable random delay algorithm, The range is waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration @nullable private Boolean enableRandomizedWait; private Double randomizedWaitFactor; @Nullable private Boolean failAfterMaxAttempts;Copy the code

With the introduction of the resilience4J-spring-boot2 dependency, all the Retryer and other resilience4J components can be configured through Properties configuration, for example:

application.yml

Resilience4j. retry: configs: default: ## Maximum retry times (including the first call) 500ms ## Enable random wait time, The range is waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration EnableRandomizedWait: true randomizedWaitFactor: 0.5 test-client1: ## Maximum number of retries including first call maxRetryAttempts: 3 ## retry waitDuration 800ms ## Enable random wait time The range is waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration EnableRandomizedWait: true randomizedWaitFactor: 0.5Copy the code

In this way, we can obtain the Retryer corresponding to the configuration with the following code:

@Autowired RetryRegistry retryRegistry; / / read resilience4j. Retry. Configs. Test - client1 configuration, build a retry, This Retry is named retry1 Retry Retry1 = retryRegistry. Retry ("retry1", "test-client1"); / / read resilience4j. Retry. Configs. The default configuration, build a retry, Retry Retry2 = retryRegistry. Retry ("retry2"); Retry retry2 = retryRegistry.Copy the code

Introducing the dependency of Resilience4J-spring-Cloud2 is equivalent to introducing the dependency of Resilience4j-spring-boot2. On this basis, the dynamic RefreshScope mechanism for spring-cloud-config is added compatibility.

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-cloud2</artifactId>
</dependency>
Copy the code

Add retries to OpenFeign using Resilience4J-feign

The resilience4J-Feign dependency library is provided

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-feign</artifactId>
</dependency>
Copy the code

Next, we use this dependency to add retries to OpenFeign, first enabling OpenFeign Client and specifying the default configuration:

OpenFeignAutoConfiguration

@EnableFeignClients(value = "com.github.jojotech", defaultConfiguration = DefaultOpenFeignConfiguration.class)
Copy the code

In this default configuration, glue Resilience4J to add retries by overriding the default feign.Builder:

@Bean public Feign.Builder resilience4jFeignBuilder( List<FeignDecoratorBuilderInterceptor> feignDecoratorBuilderInterceptors, FeignDecorators.Builder builder ) { feignDecoratorBuilderInterceptors.forEach(feignDecoratorBuilderInterceptor -> feignDecoratorBuilderInterceptor.intercept(builder)); return Resilience4jFeign.builder(builder.build()); } @Bean public FeignDecorators.Builder defaultBuilder( Environment environment, RetryRegistry retryRegistry ) { String name = environment.getProperty("feign.client.name"); Retry retry = null; try { retry = retryRegistry.retry(name, name); } catch (ConfigurationNotFoundException e) { retry = retryRegistry.retry(name); } // Override the exception judgment and retry only on feign.retryableException, All exceptions that need retries are encapsulated in Our Terrordecoder and Resilience4jFeignClient as RetryableException Retry = Retry.of(name, RetryConfig.from(retry.getRetryConfig()).retryOnException(throwable -> { return throwable instanceof feign.RetryableException; }).build()); return FeignDecorators.builder().withRetry( retry ); }Copy the code

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