如何实现重试?

Try-catch是用来帮助异常处理的。这意味着它将在某种程度上帮助我们的系统变得更加健壮:尝试从意外事件中恢复。

我们怀疑在执行和指令(发送消息)时可能会发生一些事情,因此它被包含在try中。如果发生了几乎意想不到的事情,我们可以做些什么:我们编写捕获。我不认为我们调用只是为了记录异常。我认为catch块是为了给我们从错误中恢复的机会。

现在,假设我们从错误中恢复,因为我们可以修复错误。如果能再试一次,那就太好了:

try{ some_instruction(); }
catch (NearlyUnexpectedException e){
fix_the_problem();
retry;
}

这将很快陷入永恒循环,但让我们假设fix_the_problem返回true,然后重试。假设在Java中没有这样的东西,你将如何解决这个问题?你认为解决这个问题的最佳设计代码是什么?

这就像一个哲学问题,因为我已经知道Java不直接支持我所要求的内容。

279603 次浏览

你需要像这样将你的try-catch包含在一个while循环中:-

int count = 0;
int maxTries = 3;
while(true) {
try {
// Some Code
// break out of loop, or return, on success
} catch (SomeException e) {
// handle exception
if (++count == maxTries) throw e;
}
}

我已经使用countmaxTries来避免运行到无限循环,以防异常继续发生在你的try block中。

使用带有本地status标志的while循环。初始化标志为false,并在操作成功时将其设置为true,如下所示:

  boolean success  = false;
while(!success){
try{
some_instruction();
success = true;
} catch (NearlyUnexpectedException e){
fix_the_problem();
}
}

这将不断重试,直到成功。

如果你只想重试一定次数,那么也可以使用计数器:

  boolean success  = false;
int count = 0, MAX_TRIES = 10;
while(!success && count++ < MAX_TRIES){
try{
some_instruction();
success = true;
} catch (NearlyUnexpectedException e){
fix_the_problem();
}
}
if(!success){
//It wasn't successful after 10 retries
}

这将尝试最多10次,如果不成功,直到那时将退出,如果它成功之前。

通常,最好的设计取决于特定的环境。通常我会这样写:

for (int retries = 0;; retries++) {
try {
return doSomething();
} catch (SomeException e) {
if (retries < 6) {
continue;
} else {
throw e;
}
}
}

解决这个问题的一个简单方法是在while循环中包装try/catch并维护一个计数。通过这种方式,您可以根据其他变量检查计数,同时维护故障日志,从而防止出现无限循环。这不是最精致的解决方案,但它是可行的。

Try-Catch所做的就是允许程序优雅地失败。在catch语句中,通常尝试记录错误,如果需要,可能还会回滚更改。

bool finished = false;


while(finished == false)
{
try
{
//your code here
finished = true
}
catch(exception ex)
{
log.error("there was an error, ex");
}
}

虽然try/catch转换为while是众所周知的好策略,我想建议你递归调用:

void retry(int i, int limit) {
try {


} catch (SomeException e) {
// handle exception
if (i >= limit) {
throw e;  // variant: wrap the exception, e.g. throw new RuntimeException(e);
}
retry(i++, limit);
}
}

这些答案基本上都是一样的。我的也是,但我喜欢这种形式

boolean completed = false;
Throwable lastException = null;
for (int tryCount=0; tryCount < config.MAX_SOME_OPERATION_RETRIES; tryCount++)
{
try {
completed = some_operation();
break;
}
catch (UnlikelyException e) {
lastException = e;
fix_the_problem();
}
}
if (!completed) {
reportError(lastException);
}

强制性的“企业级”解决方案:

public abstract class Operation {
abstract public void doIt();
public void handleException(Exception cause) {
//default impl: do nothing, log the exception, etc.
}
}


public class OperationHelper {
public static void doWithRetry(int maxAttempts, Operation operation) {
for (int count = 0; count < maxAttempts; count++) {
try {
operation.doIt();
count = maxAttempts; //don't retry
} catch (Exception e) {
operation.handleException(e);
}
}
}
}

并呼吁:

OperationHelper.doWithRetry(5, new Operation() {
@Override public void doIt() {
//do some stuff
}
@Override public void handleException(Exception cause) {
//recover from the Exception
}
});

你可以使用来自jcabi-aspects的AOP和Java注释(我是一个开发人员):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
return url.openConnection().getContent();
}

你也可以使用@Loggable@LogException注释。

使用do-while设计重试块。

boolean successful = false;
int maxTries = 3;
do{
try {
something();
success = true;
} catch(Me ifUCan) {
maxTries--;
}
} while (!successful || maxTries > 0)

我知道这里已经有很多类似的答案,我的答案也没有太大不同,但我还是会把它贴出来,因为它涉及一个具体的案例/问题。

在处理PHP中的facebook Graph API时,有时会得到一个错误,但立即重新尝试相同的事情将得到一个积极的结果(由于各种神奇的 Internet原因,超出了这个问题的范围)。在这种情况下,不需要修复任何错误,而只是再试一次,因为有某种“facebook错误”。

这段代码在创建facebook会话后立即使用:

//try more than once because sometimes "facebook error"
$attempt = 3;
while($attempt-- > 0)
{
// To validate the session:
try
{
$facebook_session->validate();
$attempt = 0;
}
catch (Facebook\FacebookRequestException $ex)
{
// Session not valid, Graph API returned an exception with the reason.
if($attempt <= 0){ echo $ex->getMessage(); }
}
catch (\Exception $ex)
{
// Graph API returned info, but it may mismatch the current app or have expired.
if($attempt <= 0){ echo $ex->getMessage(); }
}
}

此外,通过让for循环倒数为零($attempt--),可以非常容易地在未来改变尝试次数。

以下是我的解决方案,非常简单的方法!

               while (true) {
try {
/// Statement what may cause an error;
break;
} catch (Exception e) {


}
}

如果它是有用的,可以考虑更多的选项,所有放在一起(停止文件而不是重试,睡眠,继续更大的循环)都可能有帮助。

 bigLoop:
while(!stopFileExists()) {
try {
// do work
break;
}
catch (ExpectedExceptionType e) {


// could sleep in here, too.


// another option would be to "restart" some bigger loop, like
continue bigLoop;
}
// ... more work
}

我不确定这是否是“专业”的方式来做这件事,我不完全确定它是否适用于所有事情。

boolean gotError = false;


do {
try {
// Code You're Trying
} catch ( FileNotFoundException ex ) {
// Exception
gotError = true;
}
} while ( gotError = true );

https://github.com/tusharmndr/retry-function-wrapper/tree/master/src/main/java/io

int MAX_RETRY = 3;
RetryUtil.<Boolean>retry(MAX_RETRY,() -> {
//Function to retry
return true;
});

下面是Java 8+的可重用和更通用的方法,不需要外部库:

public interface IUnreliable<T extends Exception>
{
void tryRun ( ) throws T;
}


public static <T extends Exception> void retry (int retryCount, IUnreliable<T> runnable) throws T {
for (int retries = 0;; retries++) {
try {
runnable.tryRun();
return;
} catch (Exception e) {
if (retries < retryCount) {
continue;
} else {
throw e;
}
}
}
}

用法:

@Test
public void demo() throws IOException {
retry(3, () -> {
new File("/tmp/test.txt").createNewFile();
});
}

你可以使用https://github.com/bnsd55/RetryCatch

例子:

RetryCatch retryCatchSyncRunnable = new RetryCatch();
retryCatchSyncRunnable
// For infinite retry times, just remove this row
.retryCount(3)
// For retrying on all exceptions, just remove this row
.retryOn(ArithmeticException.class, IndexOutOfBoundsException.class)
.onSuccess(() -> System.out.println("Success, There is no result because this is a runnable."))
.onRetry((retryCount, e) -> System.out.println("Retry count: " + retryCount + ", Exception message: " + e.getMessage()))
.onFailure(e -> System.out.println("Failure: Exception message: " + e.getMessage()))
.run(new ExampleRunnable());

你可以传递自己的匿名函数而不是new ExampleRunnable()

其余解决方案的问题是,对应的函数在没有时间间隔的情况下连续尝试,从而溢出堆栈。

为什么不只是trying每秒钟和广告eternum?

这里有一个使用setTimeout和递归函数的解决方案:

(function(){
try{
Run(); //tries for the 1st time, but Run() as function is not yet defined
}
catch(e){
(function retry(){
setTimeout(function(){
try{
console.log("trying...");
Run();
console.log("success!");
}
catch(e){
retry(); //calls recursively
}
}, 1000); //tries every second
}());
}
})();






//after 5 seconds, defines Run as a global function
var Run;
setTimeout(function(){
Run = function(){};
}, 5000);

将函数Run()替换为你想每秒钟重新__abc1的函数或代码。

Spring AOP和基于注释的解决方案:

用法(@RetryOperation是作业的自定义注释):

@RetryOperation(retryCount = 1, waitSeconds = 10)
boolean someMethod() throws Exception {
}

要做到这一点,我们需要做两件事:1。注释接口;一个春天的方面。下面是实现这些的一种方法:

标注接口:

import java.lang.annotation.*;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOperation {
int retryCount();
int waitSeconds();
}

春季相位:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;


@Aspect @Component
public class RetryAspect {


private static final Logger LOGGER = LoggerFactory.getLogger(RetryAspect.class);


@Around(value = "@annotation(RetryOperation)")
public Object retryOperation(ProceedingJoinPoint joinPoint) throws Throwable {


Object response = null;
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RetryOperation annotation = method.getAnnotation(RetryOperation.class);
int retryCount = annotation.retryCount();
int waitSeconds = annotation.waitSeconds();
boolean successful = false;


do {
try {
response = joinPoint.proceed();
successful = true;
} catch (Exception ex) {
LOGGER.info("Operation failed, retries remaining: {}", retryCount);
retryCount--;
if (retryCount < 0) {
throw ex;
}
if (waitSeconds > 0) {
LOGGER.info("Waiting for {} second(s) before next retry", waitSeconds);
Thread.sleep(waitSeconds * 1000l);
}
}
} while (!successful);


return response;
}
}

这是一个老问题,但解决方案仍然重要。以下是我在Java 8中不使用任何第三方库的通用解决方案:

public interface RetryConsumer<T> {
T evaluate() throws Throwable;
}
public interface RetryPredicate<T> {
boolean shouldRetry(T t);
}
public class RetryOperation<T> {
private RetryConsumer<T> retryConsumer;
private int noOfRetry;
private int delayInterval;
private TimeUnit timeUnit;
private RetryPredicate<T> retryPredicate;
private List<Class<? extends Throwable>> exceptionList;


public static class OperationBuilder<T> {
private RetryConsumer<T> iRetryConsumer;
private int iNoOfRetry;
private int iDelayInterval;
private TimeUnit iTimeUnit;
private RetryPredicate<T> iRetryPredicate;
private Class<? extends Throwable>[] exceptionClasses;


private OperationBuilder() {
}


public OperationBuilder<T> retryConsumer(final RetryConsumer<T> retryConsumer) {
this.iRetryConsumer = retryConsumer;
return this;
}


public OperationBuilder<T> noOfRetry(final int noOfRetry) {
this.iNoOfRetry = noOfRetry;
return this;
}


public OperationBuilder<T> delayInterval(final int delayInterval, final TimeUnit timeUnit) {
this.iDelayInterval = delayInterval;
this.iTimeUnit = timeUnit;
return this;
}


public OperationBuilder<T> retryPredicate(final RetryPredicate<T> retryPredicate) {
this.iRetryPredicate = retryPredicate;
return this;
}


@SafeVarargs
public final OperationBuilder<T> retryOn(final Class<? extends Throwable>... exceptionClasses) {
this.exceptionClasses = exceptionClasses;
return this;
}


public RetryOperation<T> build() {
if (Objects.isNull(iRetryConsumer)) {
throw new RuntimeException("'#retryConsumer:RetryConsumer<T>' not set");
}


List<Class<? extends Throwable>> exceptionList = new ArrayList<>();
if (Objects.nonNull(exceptionClasses) && exceptionClasses.length > 0) {
exceptionList = Arrays.asList(exceptionClasses);
}
iNoOfRetry = iNoOfRetry == 0 ? 1 : 0;
iTimeUnit = Objects.isNull(iTimeUnit) ? TimeUnit.MILLISECONDS : iTimeUnit;
return new RetryOperation<>(iRetryConsumer, iNoOfRetry, iDelayInterval, iTimeUnit, iRetryPredicate, exceptionList);
}
}


public static <T> OperationBuilder<T> newBuilder() {
return new OperationBuilder<>();
}


private RetryOperation(RetryConsumer<T> retryConsumer, int noOfRetry, int delayInterval, TimeUnit timeUnit,
RetryPredicate<T> retryPredicate, List<Class<? extends Throwable>> exceptionList) {
this.retryConsumer = retryConsumer;
this.noOfRetry = noOfRetry;
this.delayInterval = delayInterval;
this.timeUnit = timeUnit;
this.retryPredicate = retryPredicate;
this.exceptionList = exceptionList;
}


public T retry() throws Throwable {
T result = null;
int retries = 0;
while (retries < noOfRetry) {
try {
result = retryConsumer.evaluate();
if (Objects.nonNull(retryPredicate)) {
boolean shouldItRetry = retryPredicate.shouldRetry(result);
if (shouldItRetry) {
retries = increaseRetryCountAndSleep(retries);
} else {
return result;
}
} else {
// no retry condition defined, no exception thrown. This is the desired result.
return result;
}
} catch (Throwable e) {
retries = handleException(retries, e);
}
}
return result;
}


private int handleException(int retries, Throwable e) throws Throwable {
if (exceptionList.contains(e.getClass()) || (exceptionList.isEmpty())) {
// exception is excepted, continue retry.
retries = increaseRetryCountAndSleep(retries);
if (retries == noOfRetry) {
// evaluation is throwing exception, no more retry left. Throw it.
throw e;
}
} else {
// unexpected exception, no retry required. Throw it.
throw e;
}
return retries;
}


private int increaseRetryCountAndSleep(int retries) {
retries++;
if (retries < noOfRetry && delayInterval > 0) {
try {
timeUnit.sleep(delayInterval);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
}
return retries;
}
}

让我们有一个这样的测试用例:

@Test
public void withPredicateAndException() {
AtomicInteger integer = new AtomicInteger();
try {
Integer result = RetryOperation.<Integer>newBuilder()
.retryConsumer(() -> {
int i = integer.incrementAndGet();
if (i % 2 == 1) {
throw new NumberFormatException("Very odd exception");
} else {
return i;
}
})
.noOfRetry(10)
.delayInterval(10, TimeUnit.MILLISECONDS)
.retryPredicate(value -> value <= 6)
.retryOn(NumberFormatException.class, EOFException.class)
.build()
.retry();
Assert.assertEquals(8, result.intValue());
} catch (Throwable throwable) {
Assert.fail();
}
}

如果不是所有异常都需要重试,那么只有一些例外。如果至少要尝试一次,这里有一个替代的实用方法:

void runWithRetry(Runnable runnable, Class<Exception> exClass, int maxRetries) {
Exception err = null;
do {
maxRetries--;
try {
runnable.run();
err = null;
} catch (Exception e) {
if(exClass.isAssignableFrom(e.getClass())){
err = e;
}else {
throw e;
}
}
} while (err != null && maxRetries > 0);


if (err != null) {
throw err;
}
}

用法:

    runWithRetry(() -> {
// do something
}, TimeoutException.class, 5)

尝试使用spring @Retryable注解,当RuntimeException发生时,下面的方法将重试3次

@Retryable(maxAttempts=3,value= {RuntimeException.class},backoff = @Backoff(delay = 500))
public void checkSpringRetry(String str) {
if(StringUtils.equalsIgnoreCase(str, "R")) {
LOGGER.info("Inside retry.....!!");
throw new RuntimeException();
}
}

下面的代码段执行一些代码段。如果在执行代码段时出现任何错误,请休眠M毫秒并重试。参考链接

public void retryAndExecuteErrorProneCode(int noOfTimesToRetry, CodeSnippet codeSnippet, int sleepTimeInMillis)
throws InterruptedException {


int currentExecutionCount = 0;
boolean codeExecuted = false;


while (currentExecutionCount < noOfTimesToRetry) {
try {
codeSnippet.errorProneCode();
System.out.println("Code executed successfully!!!!");
codeExecuted = true;
break;
} catch (Exception e) {
// Retry after 100 milliseconds
TimeUnit.MILLISECONDS.sleep(sleepTimeInMillis);
System.out.println(e.getMessage());
} finally {
currentExecutionCount++;
}
}


if (!codeExecuted)
throw new RuntimeException("Can't execute the code within given retries : " + noOfTimesToRetry);
}

这里是我的解决方案类似于其他人可以包装一个函数,但允许您获得函数返回值,如果它成功。

    /**
* Wraps a function with retry logic allowing exceptions to be caught and retires made.
*
* @param function the function to retry
* @param maxRetries maximum number of retires before failing
* @param delay time to wait between each retry
* @param allowedExceptionTypes exception types where if caught a retry will be performed
* @param <V> return type of the function
* @return the value returned by the function if successful
* @throws Exception Either an unexpected exception from the function or a {@link RuntimeException} if maxRetries is exceeded
*/
@SafeVarargs
public static <V> V runWithRetriesAndDelay(Callable<V> function, int maxRetries, Duration delay, Class<? extends Exception>... allowedExceptionTypes) throws Exception {
final Set<Class<? extends Exception>> exceptions = new HashSet<>(Arrays.asList(allowedExceptionTypes));
for(int i = 1; i <= maxRetries; i++) {
try {
return function.call();
} catch (Exception e) {
if(exceptions.contains(e.getClass())){
// An exception of an expected type
System.out.println("Attempt [" + i + "/" + maxRetries + "] Caught exception [" + e.getClass() + "]");
// Pause for the delay time
Thread.sleep(delay.toMillis());
}else {
// An unexpected exception type
throw e;
}
}
}
throw new RuntimeException(maxRetries + " retries exceeded");
}

将@ach之前的解决方案简化为一个文件,并使用功能接口。

public class OperationHelper {


public static void doWithRetry(int maxAttempts, Runnable operation, Consumer<Exception> handle) {
for (int count = 0; count < maxAttempts; count++) {
try {
operation.run();
count = maxAttempts; //don't retry
} catch (Exception e) {
handle.accept(e);
}
}
}
}

简单的

int MAX = 3;


int count = 0;
while (true) {
try {
...


break;
} catch (Exception e) {
if (count++ < MAX) {
continue;
}


...
        

break;
}
}

https://onlinegdb.com/a-7RsL1Gh

    public void doSomething() throws Exception{
final int MAX_TRIES = 10;
int count = 0;
      

while(count++ < MAX_TRIES){
try{
System.out.println("trying");
causeIssue(count); // throws error/exception till count 2
System.out.println("trying successful");
break; // break on success
} catch (Exception e){
System.out.println("caught, logging Exception:" + count);
} catch (Error e){
System.out.println("caught, logging Error:" + count);
}
}
}

输出:

trying
caught, logging Error:1
trying
caught, logging Error:2
trying
trying successful

此解决方案允许您配置一个可重用的功能,以便在不使用任何外部库的情况下基于特定异常进行重试

//创建一个适合你需要的函数。

@FunctionalInterface
public interface ThrowableBiFunction<U,T,R> {
R apply(U u ,T t) throws Exception;
}

//这是解决方案的关键

public interface ExceptionRetryable<T, U, R> {
    

int getRetries();
   

List<Class<? extends Exception>> getRetryableExceptions();


default R execute(ThrowableBiFunction<T, U, R> function, T t, U u) throws Exception {
int numberOfRetries = getRetries();
return execute(function, t, u, numberOfRetries);
}


default R execute(ThrowableBiFunction<T, U, R> function, T t, U u, int retryCount) throws Exception {
try {
log.info(" Attempting to execute ExceptionRetryable#execute ,Number of remaining retries {} ",retryCount);
return function.apply(t, u);
} catch (Exception e) {


log.info(" error occurred in ExceptionRetryable#execute",e);
if (retryCount == 0)
throw e;
for (Class exp : getRetryableExceptions()) {
if (e.getClass() == exp) {
return execute(function, t, u, retryCount - 1);
}
}
throw e;
}


}
}

//创建一个异常可重执行的实现

public class TestRetryable implements ExceptionRetryable<String, String, List<String>> {
@Override
public int getRetries() {
return 10;
}


@Override
public List<Class<? extends Exception>> getRetryableExceptions() {
return Arrays.asList(new Exception1().getClass(), new Exception2().getClass());
;
}
}

//最后创建一个throwablebiffunction,它封装了需要在exception上重试的代码段和ExceptionRetryable实例

 TestRetryable retryable = new TestRetryable();
ThrowableBiFunction<Integer,Long, String> testRetrablefcn = { i, l ->
// your code goes here
};
Integer i = 0;
Long l = 1l;
String output = testRetrablefcn.execute(testRetrablefcn,i,l);

生产就绪代码:

@FunctionalInterface
public interface Operation {


void doCall() throws IOException;


default void handleException(Exception e) {
//Your custom default implementation
}




public class OperationHelper {
public static void doWithRetry(int maxAttempts, Operation operation) {


for (int count = 0; count <= maxAttempts; count++) {
try {
operation.doCall();
return;
} catch (Exception e) {
if (count == maxAttempts) {
e.printStackTrace();
return;
} else {
operation.handleException(e);
}
}
}
}
}

在代码中使用默认实现:

OperationHelper.doWithRetry(10,
() -> //do your job );

当需要自定义异常句柄时使用:

OperationHelper.doWithRetry(10, new Operation() {
@Override public void doIt() {
//do some stuff
}
@Override public void handleException(Exception cause) {
//recover from the Exception
}
});