The business scenario

There is a function that needs to be implemented in the application: you need to upload data to a remote storage service and do other things if the return processing is successful. This functionality is uncomplicated and consists of two steps: the first step calls the remote Rest service logic wrapped around the processing method to return the processing result; The second step is to get the result of the first step or catch the exception. If there is an error or exception, retry the upload logic, otherwise continue the logic operation.

Solution evolution

The technical aspects of this problem are the ability to trigger retries and the efficient execution of logic in the case of retries.

Solution 1: try-catch-redo simple retry mode

On the basis of the normal upload logic, the function logic is re-executed by sleeping for a certain delay time in order to solve the invalid execution of immediate retry (assuming that the exception is caused by unstable external execution) by judging the return result or monitoring the abnormal decision whether to retry.

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName"."creativeTable");
		paramMap.put("ds"."20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if(! result) { Thread.sleep(1000); uploadToOdps(paramMap); }} catch (Exception e) {thread.sleep (1000); uploadToOdps(paramMap); // Try again}}Copy the code

Solution 2: try-catch-redo-retry strategy Retry strategy

The retries may not work. To solve this problem, try increasing the retrycount number of retries and the retry interval to increase the retry validity.

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName"."creativeTable");
		paramMap.put("ds"."20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if(! result) { reuploadToOdps(paramMap,1000L,10); }} catch (Exception e) {reuploadToOdps(paramMap,1000L,10); // Delay multiple retries}}Copy the code

Scheme 1 and scheme 2 is a problem: the normal logic and retry logic strong coupling, retry logic depends greatly on the normal logic execution as a result, the normal logic passive retry trigger, expected results to retry roots often due to the complexity of logical submerged and may lead to subsequent operations to retry logic to solve the problem of what to produce inconsistent understanding. Retry correctness is difficult to guarantee and is detrimental to operation and maintenance because the retry design relies on normal logical exceptions or guesswork at the root of the retry.

Graceful retry scheme attempt

Is there a solution that can decouple the normal logic from the retry logic and standardize the retry logic? The answer is yes: it is a retry tool based on the proxy design pattern, and we tried to use the tool to reconstruct the above scenario.

Try scenario 1: Apply the command design pattern to decouple normal and retry logic

The specific definition of command design mode is not elaborated. The main point of this scheme is that the command mode can complete the interface operation logic through the execution object, and at the same time encapsulate the processing retry logic internally, without exposing the implementation details. For the caller, it is to execute the normal logic and achieve the goal of decoupling. (Class diagram structure)

IRetry specifies the upload and retry interfaces, and its implementation class OdpsRetry encapsulates the ODPS upload logic, retry mechanism, and retry policy. At the same time, use the Recover method at the end of the recovery operation.

The Retryer responds to and processes the retry logic. The Retryer retries are handled by OdpsRetry, the implementation class of the real IRtry interface. By using the command mode, the separation of normal logic and retry logic is elegantly realized, and the separation of normal logic and retry logic is realized by constructing the role of retries, so that retry has better scalability.

Try scenario 2: Spring-Retry specification for normal and retry logic

Spring-retry is an open source tool package, currently available in version 1.1.2.RELEASE, which customizes retry operation templates to set retry policies and rollback policies. Retry execution instances at the same time to ensure thread safety. The operation examples are as follows:

public void upload(final Map<String, Object> map) throws Exception {// construct a RetryTemplate instance RetryTemplate RetryTemplate = new RetryTemplate(); SimpleRetryPolicy Policy = new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class,true)); FixedBackOffPolicy FixedBackOffPolicy = new FixedBackOffPolicy(); // Set the retry rollback policy. fixedBackOffPolicy.setBackOffPeriod(100); retryTemplate.setRetryPolicy(policy); retryTemplate.setBackOffPolicy(fixedBackOffPolicy); // Wrap the normal logic with the RetryCallback callback instance, Final RetryCallback<Object, Exception> RetryCallback = new RetryCallback<Object, Exception>() {//RetryContext Retry operation context convention, unified spring-try wrapper public ObjectdoWithRetry(RetryContext context) throws Exception {
				System.out.println("do some thing"); Exception e = uploadToOdps(map); System.out.println(context.getRetryCount()); throw e; // In particular, the source of the retry returns}} via Exception; RecoveryCallback Exited after the RecoveryCallback process ended or the maximum number of RecoveryCallback retries was reached. Final RecoveryCallback<Object> RecoveryCallback = new RecoveryCallback<Object>() { public Object recover(RetryContext context) throws Exception { System.out.println("do recory operation");
				returnnull; }}; Try {// The retrytemplate. execute(retryCallback, recoveryCallback) logic starts with the retryTemplate execute method. } catch (Exception e) { e.printStackTrace(); }}Copy the code

The RetryTemplate takes on the role of retry enforcer. It can set SimpleRetryPolicy(retry policy, retry upper limit, retry root entity), FixedBackOffPolicy (fixed fallback policy, Set the retry rollback interval. RetryTemplate performs operations through execute submission. Two class instances, RetryCallback and RecoveryCallback, are required. The RetryCallback corresponds to the RetryCallback logical instance and wraps normal functional operations. RecoveryCallback implements a recovery instance at the end of an operation.

The RetryTemplate execute is thread-safe, and the implementation logic uses ThreadLocal to hold the RetryContext execution context for each execution instance.

The Spring-Retry tool, while elegant, has two unfriendly designs: One is that the retry entity is restricted to a Throwable subclass, indicating that retries are designed for catching functional exceptions, but we want to rely on a data object entity as a retry entity, but the Sping-Retry framework must cast it to a Throwable subclass. Another is that the assertion object at the retry root uses a doWithRetry Exception instance, which does not conform to the return design of normal internal assertions.

Spring Retry advocates annotated retries of methods. The Retry logic is executed synchronously, and the “failure” of retries is Throwable. If you are trying to determine whether a Retry is needed based on the state of the returned value, you may have to judge the returned value yourself and then explicitly throw an exception.

Spring’s abstraction for Retry

“Abstraction” is an essential quality for every programmer. For my mediocre self, there is no better way to advance than to imitate and understand good source code. To do this, I rewrote the core logic… Let’s look at Spring Retry’s abstraction of “Retry.”

Retry logic

while(someCondition()) {
    try{
        doSth();
        break;    
    } catch(Throwable th) {
        modifyCondition();
        wait();
    }
}
if(stillFail) {
    doSthWhenStillFail();
}Copy the code

Synchronous Retry code can do much of this, but Spring Retry abstracts it elegantly, and while the main logic remains the same, it looks much more comfortable. The main interface abstractions are shown below:

Spring Retry related interface. JPG

  • RetryCallback: Encapsulates the business logic you need to retry (doSth above)
  • RecoverCallback: Encapsulates the business logic you need to execute after multiple retries have failed (doSthWhenStillFail above)
  • RetryContext: A Retry context that can be used to pass parameters or status between multiple Retry or Retry and Recover (multiple doSth or doSth with doSthWhenStillFail)
  • RetryOperations: Defines the basic framework (template) for “retry”, which requires RetryCallback to be passed in, optionally with RecoveryCallback;
  • RetryListener: A typical “listener” that notifies “listeners” at different stages of retry (e.g. doSth, wait, etc.)
  • RetryPolicy: A policy or condition for retries, which can be as simple as multiple retries, or retries with a specified timeout (someCondition above).
  • BackOffPolicy: rollback policy for retry when an exception occurs in the service logic execution. If we need to retry, we may have to wait a certain amount of time (the server may be too busy, or it may collapse if the retries continue without interval), but this time can be zero, fixed, or random (see the rollback strategy in TCP’s congestion control algorithm). The rollback strategy is wait().
  • RetryTemplate: a concrete implementation of RetryOperations that combines RetryListener[], BackOffPolicy, and RetryPolicy.

Try scheme three: Guava-retryer separates normal and retry logic

The Guava Retryer tool is similar to Spring-Retry in that it packages logical retries by defining the retries role. However, the Guava Retryer has a better policy definition. In addition to controlling the number of retries and retry frequency, the Guava Retryer tool is compatible with retry source definitions that support multiple exceptions or user-defined entity objects. Give the retry feature more flexibility. Guava Retryer is thread-safe, entrance to call logic used is Java. The util. Concurrent. Callable call method, the example code is as follows:

Public void uploadOdps(final Map<String, Object> Map) {// RetryerBuilder The retryer instance is constructed. You can set the retry source and support multiple retry sources. You can set the retry times or retry timeout. Retryer<Boolean> Retryer = RetryerBuilder.<Boolean> newBuilder().retryifException ().// Set the exception retry source RetryIfResult (new Predicate<Boolean>() {// Set the user-defined segment retry source, @override public Boolean apply(Boolean state) {// Note that the apply returnstrueNote Retry must be distinguished from the semantics of operation logicreturn true; }}). WithStopStrategy (StopStrategies stopAfterAttempt (5)) / / set retries 5 times, WithWaitStrategy (waitstrategies.fixedWait (100L, timeunit.milliseconds)).build(); // Set each retry interval try {// Retry entry uses call method, Using Java. Util. Concurrent. Callable < V > call method, so the implementation is thread-safe Boolean result = retryer. Call (new Callable < Boolean > () {@ Override Public Boolean call() throws Exception {try {// Special attention: returnsfalseNo retry is requiredtrueYou need to try againreturnuploadToOdps(map); } catch (Exception e) { throw new Exception(e); }}}); } catch (ExecutionException e) { } catch (RetryException ex) { } }Copy the code

Sample code principle analysis:

RetryerBuilder is a factory creator. You can customize the retry source and support multiple retry sources. You can configure the retry times or retry timeout, and you can configure the waiting interval to create Retryer instances.

The retry source of RetryerBuilder supports Exception and custom assertion objects, which are set by retryIfException and retryIfResult. Multiple and compatible objects are supported simultaneously.

The RetryerBuilder uses different policy classes to configure the wait time and retry limit, and supports no interval and fixed interval for the wait time feature.

Retryer is a retry instance that performs the operation logic through the call method while encapsulating the retry source operation.

The commonality and principle of elegant retry

  1. Normal and retry are elegantly decoupled, and retry assertion condition instances or logical exception instances are the medium through which the two communicate.
  2. Specify the retry interval, configure different retry policies, and set the retry timeout period to further ensure the retry validity and stability of the retry process.
  3. Both use the command design mode, through the delegate retry object to complete the corresponding logical operation, at the same time internal encapsulation to achieve the retry logic.
  4. Both spring-Tryer and Guava-Tryer tools are thread-safe retries that support logical correctness of retries in concurrent business scenarios.

Graceful retry applies to scenarios

  1. Unstable dependency scenarios exist in the functional logic. Retry is required to obtain the expected result or re-execution of the logic does not end immediately. Such as remote interface access, data load access, data upload verification and so on.
  2. For exception scenarios, retry is required and normal logic and retry logic are decoupled.
  3. Retry schemes can also be considered for scenarios that require data-media-based interactions and wish to test execution logic through retry polling.

The resources

https://blog.csdn.net/paul_wei2008/article/details/53871442