Подробное объяснение механизма повторных попыток Java Retry

Java

Авторские права принадлежат автору Для любой формы перепечатки, пожалуйста, свяжитесь с автором для получения разрешения и укажите источник.

Приложение должно реализовать функцию: оно должно загружать данные в сервис удаленного хранилища, и в то же время выполнять другие операции при успешной обработке возврата. Эта функция несложная и разделена на два шага: первый шаг — вызвать удаленную службу Rest для загрузки данных и последующей обработки возвращенного результата; второй шаг — получить результат первого шага или перехватить исключение, и повторите попытку, если есть ошибка или исключение. Загрузите логику, в противном случае продолжите следующую функциональную бизнес-операцию.

обычное решение

try-catch-redo простой режим повтора

На основе обычной логики загрузки упаковки определяется, следует ли повторить попытку, оценивая возвращаемый результат или отслеживая исключение. из-за нестабильного внешнего выполнения: дрожание сети), переход в спящий режим после определенного времени задержки Повторное выполнение логики функции.

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);//一次重试 
    } 
  }

стратегия «попробуй-поймай-повтори-повтор»

Приведенное выше решение может по-прежнему не выполнять повторную попытку. Чтобы решить эту проблему, попробуйте увеличить количество повторных попыток.retrycountи период интервала между попыткамиinterval, чтобы увеличить вероятность того, что повторные попытки будут действительными.

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);//延迟多次重试 
    } 
  }

Существует проблема с первым и вторым решениями: нормальная логика и логика повтора сильно связаны, логика повтора очень зависит от результата выполнения нормальной логики, а ожидаемый результат нормальной логики пассивно запускается Это может привести к непоследовательному пониманию последующих операций и сопровождению того, что должна решать логика повторных попыток. Трудно гарантировать правильность повторных попыток, и они не способствуют эксплуатации и обслуживанию, поскольку схема повторных попыток основана на нормальных логических исключениях или предположениях об первопричине повторных попыток.

Попытка изящной повторной попытки

Логическая развязка команды приложения и обычного шаблона проектирования

Конкретное определение шаблона проектирования команды не будет подробно описано.Основная идея этой схемы заключается в том, что шаблон команды может завершать логику работы интерфейса через объект выполнения и в то же время инкапсулировать логику повторной обработки внутри, без раскрытие деталей реализации.Для вызывающего объекта он выполняет обычную логику и достигает цели Для разъединения см. подробности реализации функции. (структура диаграммы классов)

IRetry определяет интерфейс загрузки и повторной попытки, а его класс реализации OdpsRetry инкапсулирует логику загрузки ODPS, а также механизм и стратегию повторных попыток. В то же время используйте метод восстановления, чтобы выполнить операцию восстановления в конце.

Наш вызывающий LogicClient не должен обращать внимание на повторную попытку и реализует функцию интерфейса контракта через повторную попытку Retryer, в то же время Retryer должен ответить и обработать логику повторной попытки, а конкретная обработка повторной попытки Retryer передается к реальному классу реализации интерфейса IRtry OdpsRetry Finish. За счет использования командного режима элегантно достигается разделение обычной логики и логики повторных попыток, и в то же время путем создания роли повторной попытки реализуется разделение обычной логики и логики повторных попыток, так что повторная попытка имеет лучшую масштабируемость.

Используйте ретривер Guava для элегантной реализации механизма перенастройки интерфейса.

Инструмент Guava retryer похож на spring-retry тем, что он оборачивает обычные логические повторы, определяя роль повторителя, но Guava retryer имеет лучшее определение политики, которое совместимо с контролем количества повторных попыток и частоты повторных попыток. Определения источника повторных попыток, которые поддерживают несколько исключений или настраиваемых объектов сущностей, обеспечивают большую гибкость в функции повторных попыток. Guava Retryer также является потокобезопасным, а логика вызова входа использует метод вызова Java.util.concurrent.Callable. Использовать ретривер Guava очень просто, нам просто нужно сделать следующие шаги:

  1. Maven Pom Введение
<guava-retry.version>2.0.0</guava-retry.version>
<dependency>
      <groupId>com.github.rholder</groupId>
      <artifactId>guava-retrying</artifactId>
      <version>${guava-retry.version}</version>
</dependency>
  1. Определите метод, который реализует интерфейс Callable, чтобы повторитель Guava мог вызывать
private static Callable<Boolean> updateReimAgentsCall = new Callable<Boolean>() {
   @Override
   public Boolean call() throws Exception {
       String url = ConfigureUtil.get(OaConstants.OA_REIM_AGENT);
       String result = HttpMethod.post(url, new ArrayList<BasicNameValuePair>());
       if(StringUtils.isEmpty(result)){
          throw new RemoteException("获取OA可报销代理人接口异常");
       }
       List<OAReimAgents> oaReimAgents = JSON.parseArray(result, OAReimAgents.class);
       if(CollectionUtils.isNotEmpty(oaReimAgents)){
           CacheUtil.put(Constants.REIM_AGENT_KEY,oaReimAgents);
           return true;
       }
       return false;
   }
};
  1. Определить объекты Retry и установить соответствующие политики
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                //抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
                .retryIfException()
                //返回false也需要重试
                .retryIfResult(Predicates.equalTo(false))
                //重调策略
                .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
                //尝试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
 
try {
    retryer.call(updateReimAgentsCall());
    # 以下方式可以不用实现第二步中所说的实现Callable接口定义方法
    //retry.call(() -> { FileUtils.downloadAttachment(projectNo, url, saveDir, fileName);  return true; });
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (RetryException e) {
    logger.error("xxx");
}

В три простых шага вы можете использовать Guava Retryer для элегантной реализации методов перенастройки.

Больше возможностей

RetryerBuilder — это создатель Factory, который может настраивать источник повторных попыток и поддерживать несколько источников повторных попыток, настраивать количество повторных попыток или тайм-аут повторных попыток, а также настраивать интервал времени ожидания для создания экземпляра Retryer для повторной попытки. Поддержка источника повторной попытки для RetryerBuilderОбъект исключения исключенияинастраиваемый объект утверждения,пройти черезretryIfException 和retryIfResultНастройка при поддержке нескольких и совместимых.

  • retryIfException: когда возникает исключение времени выполнения или проверенное исключение, оно будет повторено, но если возникнет ошибка, оно не будет повторено.
  • retryIfRuntimeException: Попытка будет повторена только при возникновении исключения времени выполнения, и ни проверенное исключение, ни ошибка не будут повторены.
  • retryIfExceptionOfType: позволяет нам повторить попытку только при возникновении определенного исключения, такого как NullPointerException и IllegalStateException, которые являются исключениями времени выполнения, включая пользовательские ошибки. как:
# 只在抛出error重试
retryIfExceptionOfType(Error.class)     
# 只有出现指定的异常的时候才重试,如:&emsp;&emsp;
retryIfExceptionOfType(IllegalStateException.class)  
retryIfExceptionOfType(NullPointerException.class)  
# 或者通过Predicate实现
retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),  
                Predicates.instanceOf(IllegalStateException.class))) 

retryIfResultВы можете указать, что метод Callable повторяет попытку при возврате значения, например   

// 返回false重试 
retryIfResult(Predicates.equalTo(false))  
//以_error结尾才重试 
retryIfResult(Predicates.containsPattern("_error$"))  

Когда происходит повторная попытка, если нам нужно выполнить некоторые дополнительные действия по обработке, такие как отправка электронного письма с предупреждением или что-то еще, мы можем использоватьRetryListener. После каждой повторной попытки guava-retry будет автоматически вызывать зарегистрированный нами слушатель. Вы также можете зарегистрировать несколько RetryListeners, которые будут вызываться в порядке регистрации.

import com.github.rholder.retry.Attempt;  
import com.github.rholder.retry.RetryListener;  
import java.util.concurrent.ExecutionException;  
  
public class MyRetryListener<Boolean> implements RetryListener {  
    @Override  
    public <Boolean> void onRetry(Attempt<Boolean> attempt) {  
        // 第几次重试,(注意:第一次重试其实是第一次调用)  
        System.out.print("[retry]time=" + attempt.getAttemptNumber());  
        // 距离第一次重试的延迟  
        System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());  
        // 重试结果: 是异常终止, 还是正常返回  
        System.out.print(",hasException=" + attempt.hasException());  
        System.out.print(",hasResult=" + attempt.hasResult());  
        // 是什么原因导致异常  
        if (attempt.hasException()) {  
            System.out.print(",causeBy=" + attempt.getExceptionCause().toString());  
        } else {  
            // 正常返回时的结果  
            System.out.print(",result=" + attempt.getResult());  
        }  
  
        // bad practice: 增加了额外的异常处理代码  
        try {  
            Boolean result = attempt.get();  
            System.out.print(",rude get=" + result);  
        } catch (ExecutionException e) {  
            System.err.println("this attempt produce exception." + e.getCause().toString());  
        }  
        System.out.println();  
    }  
} 

Далее указываем прослушиватель в объекте Retry:withRetryListener(new MyRetryListener<>())