失败的“重试”逻辑的设计模式?

时间:2012-07-27 17:19:50

标签: java design-patterns io

我正在编写一些重新连接逻辑,以定期尝试与远程端点建立连接。本质上,代码如下所示:

public void establishConnection() {
    try {
        this.connection = newConnection();
    } catch (IOException e) {
        // connection failed, try again.
        try { Thread.sleep(1000); } catch (InterruptedException e) {};

        establishConnection();
    }
}

我在很多场合用类似上面的代码解决了这个一般问题,但我对结果感到很不满意。是否有设计模式来处理这个问题?

12 个答案:

答案 0 :(得分:30)

无耻插件:我已经实现了一些类来允许重试操作。 该库尚未提供,但您可以fork it on github 。 并且存在fork

它允许使用各种灵活的策略构建Retryer。例如:

Retryer retryer = 
    RetryerBuilder.newBuilder()
                  .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECOND))
                  .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                  .retryIfExceptionOfType(IOException.class)
                  .build();

然后你可以用Retryer执行一个可调用的(或几个):

retryer.call(new Callable<Void>() {
    public Void call() throws IOException {
        connection = newConnection();
        return null;
    }
}

答案 1 :(得分:22)

您可以尝试Idempotent Retry Pattern

enter image description here

答案 2 :(得分:9)

我正在使用AOP和Java注释。在jcabi-aspects(我是开发人员)中有一个现成的机制:

@RetryOnFailure(attempts = 3, delay = 1, unit = TimeUnit.SECONDS)
public void establishConnection() {
  this.connection = newConnection();
}

PS。您也可以从Cactoos尝试RetryScalar

答案 3 :(得分:4)

使用Failsafe(此处作者):

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(IOException.class)
  .withMaxRetries(5)
  .withDelay(1, TimeUnit.SECONDS);

Failsafe.with(retryPolicy).run(() -> newConnection());

没有注释,没有魔法,不需要成为Spring应用程序等等。只是简单明了。

答案 4 :(得分:3)

一个值得结账的图书馆是Sarge,它会根据定义的计划自动执行重试。

答案 5 :(得分:2)

你可以尝试spring-retry,它有一个干净的界面,而且它很容易使用。

示例:

 @Retryable(maxAttempts = 4, backoff = @Backoff(delay = 500))
 public void establishConnection() {
    this.connection = newConnection();
 } 

如果发生异常,它将重试(调用)方法 establishConnection()最多4次,退避政策为500毫秒

答案 6 :(得分:2)

我非常喜欢this blog中的这个Java 8代码,并且您的类路径上不需要任何额外的库。

您只需将函数传递给重试类。

@Slf4j
public class RetryCommand<T> {

    private int maxRetries;

    RetryCommand(int maxRetries)
    {
        this.maxRetries = maxRetries;
    }

    // Takes a function and executes it, if fails, passes the function to the retry command
    public T run(Supplier<T> function) {
        try {
            return function.get();
        } catch (Exception e) {
            log.error("FAILED - Command failed, will be retried " + maxRetries + " times.");
            return retry(function);
        }
    }

    private T retry(Supplier<T> function) throws RuntimeException {

        int retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return function.get();
            } catch (Exception ex) {
                retryCounter++;
                log.error("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries, ex);
                if (retryCounter >= maxRetries) {
                    log.error("Max retries exceeded.");
                    break;
                }
            }
        }
        throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
    }
}

使用它:

new RetryCommand<>(5).run(() -> client.getThatThing(id));

答案 7 :(得分:1)

您还可以创建一个包装器功能,该功能仅对预期的操作进行循环,完成后只需跳出循环即可。

public static void main(String[] args) {
    retryMySpecialOperation(7);
}

private static void retryMySpecialOperation(int retries) {
    for (int i = 1; i <= retries; i++) {
        try {
            specialOperation();
            break;
        }
        catch (Exception e) {
            System.out.println(String.format("Failed operation. Retry %d", i));
        }
    }
}

private static void specialOperation() throws Exception {
    if ((int) (Math.random()*100) % 2 == 0) {
        throw new Exception("Operation failed");
    }
    System.out.println("Operation successful");
}

答案 8 :(得分:0)

重试没有什么特别之处 - 以此类为例http://www.docjar.com/html/api/org/springframework/jms/listener/DefaultMessageListenerContainer.java.html 正如你所看到的,即使是Spring开发人员仍在为重试编写代码 - 791行...... AFAIK没有这种特殊的模式..

我可以建议处理资源的方法是使用apache commons池库 - 检查http://commons.apache.org/pool/apidocs/org/apache/commons/pool/impl/GenericObjectPool.html并访问http://commons.apache.org/pool

答案 9 :(得分:0)

如果您使用的是Java 8,这可能有所帮助。

import java.util.function.Supplier;

public class Retrier {
public static <T> Object retry(Supplier<T> function, int retryCount) throws Exception {
     while (0<retryCount) {
        try {
            return function.get();
        } catch (Exception e) {
            retryCount--;
            if(retryCount == 0) {
                throw e;
            }
        }
    }
    return null;
}

public static void main(String[] args) {
    try {
        retry(()-> {
            System.out.println(5/0);
            return null;
        }, 5);
    } catch (Exception e) {
        System.out.println("Exception : " + e.getMessage());
    }
}
}

谢谢,

Praveen R。

答案 10 :(得分:0)

我正在使用retry4j库。测试代码示例:

public static void main(String[] args) {
    Callable<Object> callable = () -> {
        doSomething();
        return null;
    };

    RetryConfig config = new RetryConfigBuilder()
            .retryOnAnyException()
            .withMaxNumberOfTries(3)
            .withDelayBetweenTries(5, ChronoUnit.SECONDS)
            .withExponentialBackoff()
            .build();

    new CallExecutorBuilder<>().config(config).build().execute(callable);
}

public static void doSomething() {
    System.out.println("Trying to connect");
    // some logic
    throw new RuntimeException("Disconnected"); // init error
    // some logic
}

答案 11 :(得分:0)

这是执行重试的另一种方法。没有库,没有注释,没有额外的实现。导入 java.util.concurrent.TimeUnit;

public static void myTestFunc() {
        boolean retry = true;
        int maxRetries = 5;   //max no. of retries to be made
        int retries = 1;
        int delayBetweenRetries = 5;  // duration  between each retry (in seconds)
        int wait = 1;
    do {
        try {
            this.connection = newConnection();
            break;
        }
        catch (Exception e) {
            wait = retries * delayBetweenRetries;
            pause(wait);
            retries += 1;
            if (retries > maxRetries) {
                retry = false;
                log.error("Task failed on all of " + maxRetries + " retries");
            }
        }
    } while (retry);

}

public static void pause(int seconds) {
    long secondsVal = TimeUnit.MILLISECONDS.toMillis(seconds);

    try {
        Thread.sleep(secondsVal);
    }
    catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
}

}