Статьи

Spring Retry — как справиться с ошибками

В мире микросервисов у нас есть сервисы, разговаривающие друг с другом. Один из способов общения — синхронный . Однако в мире облачных вычислений факт заключается в том, что мы не можем избежать сбоев в работе сети, временного простоя службы (из-за перезапуска или сбоя; не более нескольких секунд). Когда клиентам нужны данные в реальном времени и ваша нисходящая служба не отвечает мгновенно, это может повлиять на пользователей, поэтому вам следует создать механизм повторных попыток. Есть много вариантов решения, доступных в Java. Я собираюсь рассказать о Spring Retry в этом блоге. Мы создадим небольшое приложение и посмотрим, как работает Spring Retry. Прежде чем мы начнем, давайте сначала разберемся с некоторыми основами  шаблона Retry :

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

  • Повторная попытка может привести к засорению ресурсов и ухудшению ситуации, препятствуя восстановлению приложения; следовательно, количество повторных попыток должно быть ограничено. Вы должны попытаться начать с минимального счета, такого как 3, и не превышать 5 или около того.

  • Повторить не следует делать для каждого исключения. Это должно быть закодировано только для определенного типа исключения. Например, вместо размещения кода вокруг Exception.class, сделайте это для SQLException.class.

  • Повторная попытка может привести к тому, что несколько потоков будут пытаться получить доступ к одному и тому же общему ресурсу, и блокировка может стать большой проблемой. Алгоритм экспоненциального отката должен применяться для постоянного увеличения задержки между попытками, пока вы не достигнете максимального предела.

  • При применении Retry идемпотентность должна быть обработана. Повторный запуск одного и того же запроса не должен инициировать повторную транзакцию в системе.

Теперь давайте создадим простой сервис, демонстрирующий, как Spring Retry помогает справиться с Retry.

Предпосылки

  • Spring Boot 2.1.x

  • Gradle

  • Visual Studio Code / Eclipse

Зависимости Gradle

dependencies {
 implementation('org.springframework.boot:spring-boot-starter-web')
 implementation('org.springframework.retry:spring-retry')
 implementation('org.springframework.boot:spring-boot-starter-aop')

 testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Spring Retry использует Spring AOP для работы, поэтому он должен быть добавлен как зависимость.

Включить повтор

Поместите аннотацию @EnableRetry в основной класс SpringBoot.

@EnableRetry
@SpringBootApplication
public class DemoApplication {

 public static void main(String[] args) {
  SpringApplication.run(DemoApplication.class, args);
 }
}

Поместите Retryable Logic в Сервис

Аннотация @Retryable должна применяться к методу, который должен иметь логику повтора. В этом коде я поместил переменную counter, чтобы показать в журналах, сколько раз он пытается выполнить логику повторных попыток.

  • Вы можете настроить, по какому исключению он должен вызвать повторную попытку.

  • Он также может определить, сколько попыток повторной попытки он может сделать. По умолчанию установлено значение 3, если вы его не определили. 

  • Метод @Recover будет вызываться после того, как все попытки повторных попыток будут исчерпаны, а служба по-прежнему выдает исключение (в данном случае SQLException). Метод восстановления должен обрабатывать резервный механизм для этих запросов.

@Service
public class BillingService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BillingService.class);
    int counter =0;

    @Retryable(value = { SQLException.class }, maxAttempts = 3)
    public String simpleRetry() throws SQLException {
        counter++;
        LOGGER.info("Billing Service Failed "+ counter);
        throw new SQLException();

    }

    @Recover
    public String recover(SQLException t){
        LOGGER.info("Service recovering");
        return "Service recovered from billing service failure.";
    }
}   

Создайте конечную точку REST для тестирования

Этот клиент создан только для того, чтобы поразить метод BillingService simpleRetry (). 

@RestController
@RequestMapping(value="/billing")
public class BillingClientService {

    @Autowired
    private BillingService billingService;
    @GetMapping
    public String callRetryService() throws SQLException {
        return billingService.simpleRetry();
    }
}

Запустите URL и посмотрите журналы

HTTP: // локальный: 8080 / биллинг

2018-11-16 09:59:51.399  INFO 17288 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 1
2018-11-16 09:59:52.401  INFO 17288 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 2
2018-11-16 09:59:53.401  INFO 17288 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 3
2018-11-16 09:59:53.402  INFO 17288 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Service recovering

Журналы показывают, что он пробовал метод simpleRetry 3 раза, а затем перенаправил его на метод восстановления.

Применить политику возврата

Как мы уже говорили выше, повторные попытки могут вызвать блокировку ресурсов, поэтому мы должны добавить политику BackOff, чтобы создать разрыв между повторными попытками. Измените код метода BillingService simpleRetry, как показано ниже:

  @Retryable(value = { SQLException.class }, maxAttempts = 3, backoff = @Backoff(delay = 5000))
    public String simpleRetry() throws SQLException {
        counter++;
        LOGGER.info("Billing Service Failed "+ counter);
        throw new SQLException();

    }

Захватите журналы, показывающие 5-секундный разрыв в каждой повторной попытке.

2018-11-17 23:02:12.491  INFO 53392 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 1
2018-11-17 23:02:17.494  INFO 53392 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 2
2018-11-17 23:02:22.497  INFO 53392 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Billing Service Failed 3
2018-11-17 23:02:22.497  INFO 53392 --- [nio-8080-exec-1] c.e.springretrydemo.BillingService       : Service recovering

Есть много других вариантов применения шаблона Retry в Java. Некоторые из них следующие:

  • AWS SDK можно использовать, только если вы используете сервис, связанный с AWS, и шлюз API AWS через API-интерфейсы AWS SDK.

  • Failsafe  — это легкая библиотека с нулевой зависимостью для обработки сбоев. Он был спроектирован так, чтобы быть максимально простым в использовании, с кратким API для обработки повседневных сценариев использования и гибкостью для обработки всего остального. 

  • Функциональный интерфейс Java 8.

Вот и все для этого блога. Дайте мне знать, какие библиотеки вы используете в микросервисах для обработки сбоев и повторных попыток.