你如何实现重试捕获?
Posted
技术标签:
【中文标题】你如何实现重试捕获?【英文标题】:How do you implement a re-try-catch? 【发布时间】:2012-10-25 18:04:25 【问题描述】:Try-catch 旨在帮助处理异常。这意味着它将以某种方式帮助我们的系统更加健壮:尝试从意外事件中恢复。
我们怀疑在执行和指令(发送消息)时可能会发生某些事情,所以它被包含在 try 中。如果发生了几乎出乎意料的事情,我们可以做一些事情:我们编写 catch。我不认为我们打电话只是为了记录异常。我认为 catch 块是为了让我们有机会从错误中恢复。
现在,假设我们从错误中恢复,因为我们可以修复错误。重试可能会非常好:
try some_instruction();
catch (NearlyUnexpectedException e)
fix_the_problem();
retry;
这将很快陷入永恒循环,但假设 fix_the_problem 返回 true,然后我们重试。鉴于Java中没有这样的东西,你将如何解决这个问题?解决这个问题的最佳设计代码是什么?
这就像一个哲学问题,因为我已经知道我所要求的不是 Java 直接支持的。
【问题讨论】:
这是什么异常? 我喜欢你的例外的名字。 ;) 事实上,您可以从中恢复的异常并不多。我承认我最初的动机并不是一个真正的例外,而是避免这种情况几乎永远不会发生的方法:我尝试从java.util.Queue
到remove()
,当队列为空时,这会触发和InvalidElementException
。我没有询问它是否为空,而是将动作包围在一个 try-catch 中(即使在前一个 if 的情况下,它在并发下也成为强制性的)。在这种情况下,在catch
块中,我会要求用更多元素重新填充队列,然后重试。瞧。
我可以看到执行此操作的通常方法是访问数据库,如果连接失败重新连接,如果失败则抛出重大异常,否则重试调用。如前所述,我们可以在循环中执行此操作,并在底部检查 if(error0) 然后返回,否则 break;
【参考方案1】:
您需要将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;
我采取了count
和maxTries
来避免陷入无限循环,以防您的try block
中继续发生异常。
【讨论】:
我起初是这样想的,没有 maxTries。感谢您的回答! @AndresFarias .. 是的,这个答案中最重要的一点是包含maxTries
。否则,如果用户不断输入错误,它将运行到infinite loop
,因此不会退出。不过不客气。 :)
谢谢你 - 它让我不必编写一些非常粗糙的代码!
是否可以在此处的 catch 中添加 Thread.sleep() 函数。因为在某些情况下,例如在 Selenium 库中等待页面响应变得至关重要。谢谢。
效果很好!对于初学者:如果你得到积极的无限循环,请检查你是否添加了“break;”在“try”块的末尾。【参考方案2】:
强制性“企业”解决方案:
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
);
【讨论】:
如果最后一次重试失败,您应该重新抛出异常,如给出的其他答案中所做的那样。【参考方案3】:像往常一样,最佳设计取决于特定情况。不过,通常我会写如下内容:
for (int retries = 0;; retries++)
try
return doSomething();
catch (SomeException e)
if (retries < 6)
continue;
else
throw e;
【讨论】:
等等,为什么在 for 循环声明中没有条件,例如:for(int retries = 0; retries 因为我只想在最后一次尝试中抛出,因此 catch 块需要那个条件,使得 for 中的条件变得多余。 我不认为那里需要continue
。你可以简单地翻转if条件。【参考方案4】:
您通过Failsafe处理的确切场景:
RetryPolicy retryPolicy = new RetryPolicy()
.retryOn(NearlyUnexpectedException.class);
Failsafe.with(retryPolicy)
.onRetry((r, f) -> fix_the_problem())
.run(() -> some_instruction());
很简单。
【讨论】:
非常好的图书馆。 对于那些想知道的人,您将在您的 gradle 依赖项中需要它 - 编译 'net.jodah:failsafe:1.1.0'【参考方案5】:虽然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);
【讨论】:
对于这个用例,递归比循环有什么好处? 这个堆栈跟踪可能看起来有点奇怪,因为它不会有limit
递归方法的计数吗?与循环版本相反,它将抛出“原始”级别......
在纸上看起来确实很优雅,但我不确定递归是否是正确的方法。
我也不明白这里为什么要递归。无论如何,我认为可以简化为:void retry(int times) (...) if (times==0) throw w; retry(times--);
使用递归代替单纯的迭代是不好的做法。当您想要推送和弹出一些数据时使用递归。【参考方案6】:
您可以使用来自jcabi-aspects(我是开发人员)的 AOP 和 Java 注释:
@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url)
return url.openConnection().getContent();
您还可以使用@Loggable
和@LogException
注释。
【讨论】:
哇!听起来很花哨! :) 应该是最佳答案。 有没有办法在尝试失败时“修复”错误(做一些可能修复下一次尝试的采用)?见问题:fix_the_problem();
在 catch 块中
鉴于未解决的问题数量和确认的错误未得到修复的时间,我不会依赖这个库。【参考方案7】:
这些答案中的大多数基本上是相同的。我的也是,不过这是我喜欢的形式
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);
【讨论】:
一个缺点是您在最后一次尝试后还会调用fix_the_problem
。这可能是一项昂贵的操作,并且可能会浪费一些时间。
@JoachimSauer 是的。你可以if (tryCount < max) fix()
——但这是一般方法的格式;细节将取决于具体情况。还有一个基于番石榴的Retryer 我一直在看。【参考方案8】:
使用带有本地 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 次,如果之前成功则将退出。
【讨论】:
与其在 while 中检查!success
,不如在成功为真时退出 while。
@RohitJain:对我来说它看起来更干净。
@YogendraSingh.. 奇怪。因为您没有在catch
的任何地方修改您的success
。因此,在每次运行 catch
时检查它似乎是多余的。
@RohitJain:Catch 只是更正数据。它将返回并再次运行该语句。如果成功,它将修改success
。试试看。【参考方案9】:
Spring AOP 和基于注解的解决方案:
用法(@RetryOperation
是我们对作业的自定义注释):
@RetryOperation(retryCount = 1, waitSeconds = 10)
boolean someMethod() throws Exception
我们需要两件事来完成这个:1. 一个注解接口,2. 一个 spring 方面。这是实现这些的一种方法:
注解接口:
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;
【讨论】:
【参考方案10】:这是一个老问题,但解决方案仍然相关。这是我在 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();
【讨论】:
好主意,一个建设者!【参考方案11】:解决此问题的一种简单方法是将 try/catch 包装在 while 循环中并保持计数。这样,您可以通过检查其他变量的计数来防止无限循环,同时维护您的失败日志。这不是最精致的解决方案,但它会起作用。
【讨论】:
【参考方案12】:如果有用,可以考虑更多选项,所有选项都放在一起(停止文件而不是重试、睡眠、继续更大的循环)都可能有用。
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
【讨论】:
投反对票的请留下 cmets 原因,谢谢! 这完全是无知投反对票并且没有给出理由。 在那里睡觉并不明显,因为while循环不会等待【参考方案13】:如果不是所有异常都需要重试,那么只有一些。如果必须至少尝试一次,这里有一种替代实用方法:
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)
【讨论】:
【参考方案14】: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");
【讨论】:
你的意思是与(!finished)
相对吗?
@RohitJain 它看起来太像while(finished)
。我更喜欢使用更详细的版本。
while(!finished)
到底怎么看起来像while (finished)
??
@Rohit 因为只有一个字符不同。它们都被编译成同一个东西。在 C# 中,我使用字符串扩展方法 IsPopulated()
,它只返回 !IsNullOrEmpty()
,以确保所有开发人员都能理解我的意图。【参考方案15】:
使用do-while来设计重试块。
boolean successful = false;
int maxTries = 3;
do
try
something();
success = true;
catch(Me ifUCan)
maxTries--;
while (!successful || maxTries > 0)
【讨论】:
如果不成功,代码应该抛出原始异常【参考方案16】:这里是 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();
);
【讨论】:
【参考方案17】:您可以使用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()
。
【讨论】:
【参考方案18】:将@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);
【讨论】:
【参考方案19】:我知道这里已经有很多类似的答案,我的答案也没有太大的不同,但我还是会发布它,因为它涉及特定的案例/问题。
在处理php
中的facebook Graph API
时,您有时会遇到错误,但立即重新尝试相同的事情会得到积极的结果(出于各种神奇的互联网原因,这些原因超出了这个问题的范围)。在这种情况下,没有必要修复任何错误,只需再试一次,因为存在某种“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--
),以后可以很容易地更改尝试次数。
【讨论】:
【参考方案20】:以下是我的解决方案,方法非常简单!
while (true)
try
/// Statement what may cause an error;
break;
catch (Exception e)
【讨论】:
请查看@Rohit Jain 的答案,该答案更具体,在否定情况下不是无限循环。【参考方案21】:我不确定这是否是“专业”的方式,我也不完全确定它是否适用于所有事情。
boolean gotError = false;
do
try
// Code You're Trying
catch ( FileNotFoundException ex )
// Exception
gotError = true;
while ( gotError = true );
【讨论】:
【参考方案22】: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;
);
【讨论】:
【参考方案23】:其余解决方案的问题是,对应函数连续尝试,中间没有时间间隔,因此堆栈溢出。
为什么不只是 try
ing 每秒和 ad 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()
替换为您希望每秒重新try
的函数或代码。
【讨论】:
【参考方案24】:使用springs@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();
【讨论】:
【参考方案25】:在sn-p下面执行一些代码sn-p。如果您在执行代码 sn-p 时遇到任何错误,请休眠 M 毫秒并重试。参考link。
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);
【讨论】:
【参考方案26】:这是我的解决方案,类似于其他一些可以包装函数的解决方案,但如果成功,您可以获取函数的返回值。
/**
* 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");
【讨论】:
【参考方案27】:简单
int MAX = 3;
int count = 0;
while (true)
try
...
break;
catch (Exception e)
if (count++ < MAX)
continue;
...
break;
【讨论】:
【参考方案28】: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
【讨论】:
以上是关于你如何实现重试捕获?的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud Feign 重试机制-如何实现请求重试