Java s retry mechanism for fault tolerant programming

Mondo Technology Updated on 2024-02-01

Ali Mei guide.

Fault-tolerant programming is an important programming idea that can improve the reliability and stability of an application, while improving the robustness of the application. This article summarizes how some authors perform graceful retries in the face of service failures, such as AOP and CGLIB, and summarizes and analyzes the source code and precautions of the retry tool components. Fault-tolerant programming is a programming idea that aims to ensure the reliability and stability of an application, and it takes the following measures:1Exception handling: Avoid application crashes by catching and handling exceptions.

2.Error handling: Handle errors by checking for errors** and taking appropriate action, such as retries or rollbacks.

3.Retry mechanism: In the event of an error, try to re-execute the ** block until it succeeds or reaches the maximum number of attempts.

4.Backup mechanism: In the event of a primary system failure, switch to a standby system to keep the application up and running.

5.Logging: Records error and exception information for subsequent troubleshooting. Fault-tolerant programming is an important programming idea that can improve the reliability and stability of an application, while improving the robustness of the application. 1. Why do you need to try again?

When doing business technology, it is very important to design a system architecture that is reusable, scalable, and orchestratable, which directly determines the efficiency of business demand iteration. However, at the same time, business and technical personnel should also have a pessimistic thinking: in a distributed environment, it is not uncommon for HSF services to be instantaneously jittered due to single-point problems, such as system instantaneous jitter, single point of failure, service timeout, service exception, middleware jitter, network timeout, configuration error, and other software and hardware problems. Ignoring these anomalies can reduce the robustness of the service, affecting the user experience, causing user complaints, and even system failures. Therefore, when designing and implementing the solution, it is necessary to fully consider various failure scenarios and do defensive programming in a targeted manner. When we call third-party APIs, we often encounter failure situations, and for these cases, we usually deal with failure retry and failure drop logic. However, retries are not applicable to all scenarios, such as invalid parameter validation, whether read and write operations are suitable for retry, and whether data is idempotent. If the remote call times out or the network is interrupted suddenly, you can retry. We can set multiple retries to increase the probability that the call will succeed. In order to facilitate follow-up troubleshooting and statistics on the failure rate, we can also record the number of failures and whether the success is in the database, so that statistics and scheduled tasks can be retried. The training R&D team has fully rehearsed various failure scenarios in multiple businesses such as base camp, training business, and local e-station, and has done various forms of failure retry processing for some of the core processes, such as rider test submission, synchronization of data to the insight platform, and obtaining quantum platform circle tags. This article summarizes some of the ways to perform graceful retries in the face of service failures, such as AOP and CGLIB, and summarizes and analyzes the source code and precautions of the retry tool components.

2. How to try again.

2.1 Simple retry method

Test the demo

@testpublic integer sampleretry(int code) catch (exception e)system.out.println("sampleretry, return! ");return null;
2.2 Dynamic mode version

In some cases where an object does not fit or can directly reference another object, we can use an object as an intermediary that communicates between the client and the target object. The advantage of using the object is that it is compatible and can be called by every retry method.

How to use:

public class dynamicproxytest implements invocationhandler

Get Updatesparam realsubject objectpublic static object getproxy(object realsubject)

@overridepublic object invoke(object proxy, method method, object args) throws throwable catch (exception e)

return null;

Test the demo

@testpublic integer v2retry(int code)
2.3 Bytecode technology generates ** retries

CGLIB is a build library that extends jA classes and implements interfaces at runtime. It is powerful, high-performance, and high-quality. With CGLIB, you can generate subclasses to target objects, extending and enhancing them without changing the original class. This technique is widely used in AOP frameworks, ORM frameworks, caching frameworks, and many other J**A applications. CGLIB creates ** classes by generating bytecode, which has high performance.

How to use:

public class cglibproxytest implements methodinterceptor catch (exception e)return null;

Get the class* param clazz class informationreturn class resultspublic object getproxy(class clazz)

Test the demo

@testpublic integer cglibretry(int code)
2.4 HSF call timed out retry

In our day-to-day development, it is common to have transient jitter when calling third-party HSF services. In order to reduce the impact of call timeout on the business, we can use HSF synchronous retry based on the characteristics of the service and downstream services. If the framework used is not specifically set, the HSF interface timeout will not be automatically retried by default. In annotation @hsfconsumer, there is a parameter retries, which allows you to set the number of failed retries. By default, the value of this parameter is 0 by default.

@hsfconsumer(serviceversion = "1.0.0", servicegroup = "hsf",clienttimeout = 2000, methodspecials = )private xxxhsfservice xxxhsfserviceconsumer;
Schematic diagram of the service invocation process of HSFCer timeout retry principle:

HSF timeout retries occur when asynctosyncinvocationhandler invoketype(.).If the configured retries parameter is greater than 0, the retry() method is used for retry, and the retry occurs only in the event of a retryTimeOutExceptioncase. Source code analysis.

private rpcresult invoketype(invocation invocation, invocationhandler invocationhandler) throws throwable else if (consumermethodmodel.getexecutetimes() 1) else} elsehsfresponse hsfresponse = new hsfresponse();hsfresponse.setappresponse(appresponse);

rpcresult rpcresult = new rpcresult();rpcresult.sethsfresponse(hsfresponse);return rpcresult;

As you can see from the above paragraph, retry only happens in synchronous calls. The number of times the metadata of a consumer method is executed is greater than 1 (consumermethodmodel.).getexecutetimes() 1) to try to retry:
private rpcresult retry(invocation invocation, invocationhandler invocationhandler,listenablefuture future, int executetimes) throws throwable

int timeout = -1;try catch (executionexception e) catch (timeoutexception e) else} catch (throwable e)

The hsfconsumer timeout retry principle makes use of a simple while loop + try-catch defect: 1. Retry will only occur when the method is called synchronously.

2. The retry method will be called only when the HSF interface has a timeoutexception.

3. If the retries parameter is set for a method in an HSFCeuser, a timeout exception occurs when the method returns, and the HSF SDK will automatically retry. The way the retry is implemented is a while+ try-catch loop. So, if the auto-retry interface becomes slow and the number of retries is set too high, the RT will become longer and, in extreme cases, the HSF thread pool will be full. Therefore, the automatic retry feature of HSF is a basic and simple capability that is not recommended for large-scale use.

2.5 spring retry

Spring Retry is a sub-project in the Spring family that provides declarative retry support to help us handle retries for any particular operation in a standardized way. This framework is ideal for business scenarios that require retry, such as network requests and database accesses. With Spring Retry, we can use annotations to set up a retry policy without having to write a lengthy **. All configurations are annotation-based, which makes working with Spring Retry very simple and intuitive. POM dependency.

org.springframework.retrygroupid>spring-retryartifactid>dependency>org.springframeworkgroupid>spring-aspectsartifactid>dependency>
After @retryable is enabled, the spring-retry jar package is introduced, and a @enableretry annotation is added to the startup class of Spring Boot.
@enableretry@springbootapplication(scanbasepackages = ,excludename = )@importresource()public class application
Service implementation class @retryable annotations
@override@retryable(value = bizexception.class, maxattempts = 6)public integer retryabletest(integer code)baseresponse objectbaseresponse = responsehandler.servicefailure(responseerrorenum.update_comment_failure);

system.out.println("retryabletest, correct! ");return 200;

@recoverpublic integer recover(bizexception e) ;

You can see that in **, the implementation method is annotated @retryable, and @retryable have the following parameters that can be configured:

spring-retry also provides @recover annotations for @retryable retry failure processing methods. If you don't need the ** method, you can directly not write the ** method, then the effect is that after the number of retries is finished, if it still does not succeed and does not meet the business judgment, an exception will be thrown. You can see that the pass parameter says bizexception e, which is used as the connector code of ** (the number of retries is used up, or fails, we throw this bizexception e notification to trigger this ** method).

Note: @recover annotation to enable the method called after the retry fails, the method parameter of the annotation must be the exception thrown by the @retryable, otherwise it cannot be recognized. The return value of the method @recover labeled must be the same as the method @retryable labeled. This method is written in the same implementation class as the retry method. Since it is based on the AOP implementation, it does not support self-calling methods in classes. You can't use try catch in the method, you can only throw an exception outward, and the exception must be of type throwable. Principle spring-retyr calls the sequence diagram:

The basic principle of Spring Retry is to introduce AOP capabilities through @enableretry annotations. When the Spring container starts, all methods annotated with @retryable and @circuitbreaker (fuses) are scanned and pointcuts and advice are generated. When a method call occurs, Spring delegates the RetryOperationsInterceptor to make the call, and internally implements the failback retry and downgrade recovery methods. This design pattern makes the implementation of the retry logic very simple and easy to understand, and makes full use of the AOP capabilities provided by the Spring framework to achieve an efficient and elegant retry mechanism. DefectsAlthough the Spring Retry tool is able to implement retries elegantly, it still has two less friendly designs: first, the retry entity is qualified to a Throwable subclass, which means that the retry is for a catchable functional exception, but in reality we may want to rely on a data object entity as a retry entity, but the Spring Retry framework must force it to be converted to a Throwable subclass. Second, the retry root assertion object uses an exception instance of dowithretry, which is not in line with the return design of a normal internal assertion. Spring Retry recommends using annotations to retry methods, and the retry logic is executed synchronously. The "failure" of a retry is a throwable exception, and if you want to judge whether you need to retry by a certain state of the return value, you may need to judge the return value yourself and throw the exception manually.

2.6 gu**a retrying

Gu**A Retrying is a library based on the retry mechanism of Google's core class library Gu**A, which provides a general way to retry any JA using the specific stop, retries, and exception handling capabilities enhanced by GU**A Predicate Matching, which supports multiple retry strategies, such as specifying the number of retries, specifying the retry interval, and so on. In addition, it supports predicate matching to determine if and what should be done when retrying. The best feature of gu**a retrying is its flexibility to integrate with other gu**a libraries, which makes it very easy to use. POM dependency.

com.github.rholdergroupid>gu**a-retryingartifactid>2.0.0version>dependency>
Test the demo
public static void main(string args)

retryer retryer = retryerbuilder.newbuilder()retryif retry condition.retryifexception().retryifruntimeexception().retryifexceptionoftype(exception.class).retryifexception(predicates.equalto(new exception()).retryifresult(predicates.equalto(false))Wait Policy: 1 second interval between each request.withwaitstrategy(waitstrategies.fixedwait(1, timeunit.seconds))Stop Policy : Attempt to request 6 times.withstopstrategy(stopstrategies.stopafterattempt(6))Time Limit: A request must not exceed 2 seconds.withattempttimelimiter(attempttimelimiters.fixedtimelimit(2, timeunit.seconds))Registering a custom *** can implement a fallback method after failure)..withretrylistener(new myretrylistener())build();try catch (exception ee)

When a retry occurs, if we need to do some additional actions, such as sending an alarm email, we can use retrylistener. After each retry, gu**a-retrying will automatically ** our registered listeners. Multiple retrylisteners can be registered, and they are called in the order in which they are registered.
public class myretrylistener implements retrylistener elsesystem.out.println();
RetryerBuilder is a factory creator that can customize the retry source and support multiple retry sources, configure the number of retries or the retry timeout time, and configure the wait interval to create a retryer instance.

The retry source of RetryerBuilder supports exception exception objects and custom assertion objects, and multiple and compatible objects are supported by retryifexception and retryifresult settings. RetryifException, which will be retried when a runtime exception or checked exception is thrown, but will not be retried when an error is thrown.

RetryifRuntimeException will be retried only when a runtime exception is thrown, and neither the checked exception nor the error will be retried.

RetryifExceptionOfType allows us to retry only when a specific exception occurs, such as NullPointerException and IllegalStateException, both of which are runtime exceptions, as well as custom errors.

retryifresult specifies that your callable method will retry when the value is returned.

stopstrategy: the stop-retry policy is provided in the following ways:

The advantage gu**a retryer tool is similar to spring retry in that it wraps normal logical retries by defining a retryer role. However, gu**a retryer is superior in terms of policy definition. Not only does it allow you to set the number and frequency of retries to control it, but it also provides more flexibility by being compatible with multiple exceptions or custom entity object retry source definitions. This enables gu**a retryer to be applied to a wider range of business scenarios, such as network requests, database access, and more. In addition, Gu**A Retryer is highly extensible and can be easily integrated with other Gu**A libraries. 3. Graceful retry commonality and principle.

Spring Retry and Gu**A Retryer are both thread-safe retry tools that support retry logic in concurrent business scenarios and ensure the correctness of retry. These tools can set retry intervals, differentiated retry policies, and retry timeouts, further improving the effectiveness of retries and the stability of the process. At the same time, both Spring Retry and Gu**A Retryer use the command design pattern to complete the corresponding logical operations by delecating the retry object, and implement the encapsulation of the retry logic internally. This design pattern makes it very easy to extend and modify the retry logic, while also enhancing the reusability. Fourth, summary.

In some functional logic, there are scenarios where there are unstable dependencies, and then we need to use a retry mechanism to get the expected result or try to re-execute the logic without ending immediately. For example, you may need to use the retry mechanism in scenarios such as remote interface access, data loading access, and data upload verification. Different exception scenarios require different retries in different ways, and we should decouple the normal logic from the retry logic as much as possible. When setting up a retry policy, we need to consider some issues based on the actual situation. For example, when is it appropriate to retry? Should I try synchronously, blocking, retrying, or asynchronous delay? Do you have the ability to fail quickly with one click? In addition, we need to consider whether failure without retry will seriously affect the user experience. We also need to carefully consider the above issues when setting the timeout period, retry policy, retry scenario, and number of retries. This article only explains a small part of the retry mechanism, and we should adopt an appropriate failure retry scheme according to the actual situation in practical application. Reference Documentation:gu**a-retryingImplement retry mechanism: Retry for Failed Programming: Component @recover failure problem solving: boot A note, elegant implementation of the retry mechanism: **Parallel Mao: This article takes you to explore the implementation principle of HSF: automatic retry: HSF spring-retry resilience4j Self-developed gadget:

Related Pages