Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность.
предисловие
Xiao Hei столкнулся с проблемой во время разработки: модуль, за который я отвечаю, должен вызывать сторонний сервисный интерфейс для запроса информации, и результат запроса напрямую влияет на обработку последующей бизнес-логики;
Этот интерфейс время от времени отключается из-за проблем с сетью, из-за чего моя бизнес-логика не может продолжать обработку;
Как решить эту проблему? , первой мыслью Сяо Хэя было попробовать еще раз.
Вопрос в том, а если опять не получится? Затем повторите попытку. Мы зацикливаем обработку, например, зацикливаем 5 раз, если все не удается, служба задач недоступна, и вызов завершается.
Что, если я подумаю о 5 звонках с периодом времени? Первый раз 1 секунда, потом 3 секунды, потом 5 секунд?
Сяо Хей обнаружил, что все не так просто, и если вы делаете это сами, то легко наделать ошибок.
Если подумать, то это обычное дело, в интернете должны быть колеса, поищите. Случайно позвольте мне найти его, ха-ха.
Guava Retryer
Это небольшое расширение библиотеки Google Guava, позволяющее создавать настраиваемые стратегии повторных попыток для произвольного вызова функции, например, что-то, что взаимодействует с удаленной службой с непостоянным временем безотказной работы.
Используя Guava Retryer, вы можете настроить выполнение повторных попыток, а также можете отслеживать результаты и поведение каждой повторной попытки, и, что наиболее важно, метод повторных попыток в стиле Guava действительно удобен.
импортировать зависимости
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
быстрый старт
Callable<Boolean> callable = new Callable<Boolean>() {
public Boolean call() throws Exception {
return true; // do something useful here
}
};
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfResult(Predicates.<Boolean>isNull()) // callable返回null时重试
.retryIfExceptionOfType(IOException.class) // callable抛出IOException重试
.retryIfRuntimeException() // callable抛出RuntimeException重试
.withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 重试3次后停止
.build();
try {
retryer.call(callable);
} catch (RetryException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
существуетCallable
изcall()
метод возвращает ноль, выдаетIOException
илиRuntimeException
будет повторять попытку; остановится после 3 попыток и выдаст сообщение, содержащее информацию о последней неудачной попыткеRetryException
; если в методе call() возникнет какое-либо другое исключение, оно будет упаковано и помещено вExecutionException
Напомним в .
Экспоненциальный откат
Согласно викиExponential backoff
Объяснение: экспоненциальная компенсация — это алгоритм, который экспоненциально снижает скорость процесса с помощью обратной связи, чтобы постепенно найти подходящую скорость.
В Ethernet этот алгоритм обычно используется для запланированных повторных передач после коллизий. Отложенная повторная передача определяется на основе временного интервала и количества попыток повторной передачи.
существуетc
После коллизии (например, неудачный запрос) 0 и2^c - 1
Случайное значение между как количество слотов.
Для первой коллизии каждый отправитель будет ждать 0 или 1 временной интервал для отправки. И после 2-го столкновения отправитель будет ждать от 0 до 3 (по2^2 -1
Расчетные) временные интервалы для отправки. А после 3-го столкновения отправитель будет ждать от 0 до 7 (по2^3 - 1
Расчетные) временные интервалы для отправки. И так далее...
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfExceptionOfType(IOException.class)
.retryIfRuntimeException()
.withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES)) // 指数退避
.withStopStrategy(StopStrategies.neverStop()) // 永远不停止重试
.build();
Создайте повторную попытку, которая повторяется бесконечно, увеличиваясь с экспоненциальным интервалом отсрочки после каждой неудачной попытки, максимум до 5 минут. Через 5 минут повторяйте попытку каждые 5 минут.
Откат Фибоначчи
Последовательность ФибоначчиОтносится к такой последовательности:
0,1,1,2,3,5,8,13,21,34,55,89...
Последовательность начинается с термина 3, и каждый член равен сумме двух предыдущих.
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfExceptionOfType(IOException.class)
.retryIfRuntimeException()
.withWaitStrategy(WaitStrategies.fibonacciWait(100, 2, TimeUnit.MINUTES)) // 斐波那契退避
.withStopStrategy(StopStrategies.neverStop())
.build();
Создайте повторную попытку, которая повторяется бесконечно, ожидая с шагом интервала отсрочки Фибоначчи после каждой неудачной повторной попытки, максимум до 2 минут. Через 2 минуты повторяйте попытку каждые 2 минуты.
Подобно стратегии экспоненциальной отсрочки, стратегия отсрочки Фибоначчи следует схеме ожидания все дольше и дольше после каждой неудачной попытки.
Для проверки эффективности этих двух стратегий Университет Лидса в Соединенном Королевстве провел тест производительности.По сравнению со стратегией экспоненциального отката, стратегия отсрочки Фибоначчи может иметь лучшую производительность и лучшую пропускную способность.
повторите прослушиватель
Когда происходит повторная попытка, если вам нужно выполнить некоторые дополнительные действия, такие как отправка уведомления по электронной почте, вы можете передатьRetryListener
, Guava Retryer автоматически перезванивает слушателю после каждой повторной попытки и поддерживает регистрацию нескольких слушателей.
@Slf4j
class DiyRetryListener<Boolean> implements RetryListener {
@Override
public <Boolean> void onRetry(Attempt<Boolean> attempt) {
log.info("重试次数:{}",attempt.getAttemptNumber());
log.info("距离第一次重试的延迟:{}",attempt.getDelaySinceFirstAttempt());
if(attempt.hasException()){
log.error("异常原因:",attempt.getExceptionCause());
}else {
System.out.println("正常处理结果:{}" + attempt.getResult());
}
}
}
После определения слушателя его необходимо зарегистрировать в Retryer.
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfResult(Predicates.<Boolean>isNull()) // callable返回null时重试
.retryIfExceptionOfType(IOException.class) // callable抛出IOException重试
.retryIfRuntimeException() // callable抛出RuntimeException重试
.withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 重试3次后停止
.withRetryListener(new DiyRetryListener<Boolean>()) // 注册监听器
.build();
резюме
Guava Retryer не только поддерживает различные стратегии повторных попыток, но и помещает обработку бизнес-логики вCallable
, отдельно от логики обработки повторных попыток для достижения развязки, которая намного лучше, чем собственная обработка цикла записи Сяо Хэя, Guava действительно мощная.