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

我正在编写一些重新连接逻辑,以定期尝试建立到一个远程端点的连接,该端点出现故障。实际上,代码看起来是这样的:

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


establishConnection();
}
}

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

66617 次浏览

你可以试试 幂等重试模式

enter image description here

重试根本没有什么特别的——以这个类为例,它是一个 http://www.docjar.com/html/api/org/springframework/jms/listener/defaultmessagelistenercontainer.java.html 正如您所看到的,即使是春季开发人员也仍然在为重试行791编写代码..。 没有这种特殊的模式 AFAIK. 。

我可以建议处理资源是采取 apache 公共池库-检查这个 http://commons.apache.org/pool/apidocs/org/apache/commons/pool/impl/GenericObjectPool.html和访问 http://commons.apache.org/pool

无耻的插件: 我已经实现了一些允许重试操作的类。 叉子是存在的。

它允许构建一个具有多种灵活策略的 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;
}
}

我使用的是 AOP 和 Java 注释,在 Jcabi 方面中有一个现成的机制(我是一个开发人员) :

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

你也可以尝试从 仙人掌RetryScalar

您可以尝试 春季再试,它有一个干净的界面,它很容易使用。

例如:

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

在出现异常的情况下,它将重试(调用)最多为方法 建立连接()的4倍,退出策略为500ms

使用 故障保护(点击此处查看作者) :

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


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

没有注释,没有魔术,不需要是一个 Spring 应用程序,等等,只是简单明了。

如果您正在使用 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.

我非常喜欢这个来自 这个博客的 Java8代码,而且您不需要在类路径上添加任何额外的库。

您只需要向重试类传递一个函数。

@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));

您还可以创建一个包装函式,它只是在预期的操作上执行一个循环,完成之后就从循环中断开。

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");
}

我使用的是 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
}

下面是另一种执行重试的方法。没有库,没有注释,没有额外的实现

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();
}
}

}

我已经写了我的自定义注释。也许你可以使用这个注释。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOperation {
 

int retryCount();
 

int waitSeconds();
}


@Slf4j
@Aspect
@Component
public class RetryOperationAspect {
 

@Around(value = "@annotation(com.demo.infra.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 e) {
log.error("Operation failed, retries remaining: {}", retryCount);
retryCount--;
if (retryCount < 0) {
throw e;
}
if (waitSeconds > 0) {
log.warn("Waiting for {} second(s) before next retry", waitSeconds);
Thread.sleep(waitSeconds * 1000L);
}
}
} while (!successful);
 

return response;
}
}




@RetryOperation(retryCount = 5, waitSeconds = 1)
public void method() {
         

}