JavaRush /Курсы /Модуль 5. Spring /Лекция 237: Практика: настройка политики повторных запрос...

Лекция 237: Практика: настройка политики повторных запросов и тайм-аутов

Модуль 5. Spring
24 уровень , 6 лекция
Открыта

Мы разобрались с теорией Retry и Timeout: узнали, когда их использовать, как они помогают справляться с временными сбоями и почему они так важны в микросервисной архитектуре. Сегодня закатываем рукава и пишем код! Мы реализуем все эти механизмы с помощью Spring Boot и библиотеки Resilience4j.


Подготовка окружения

Начнём с самого важного — с подготовки нашего проекта. Как говорится, хороший повар перед готовкой раскладывает все ингредиенты на столе. Мы поступим так же!

Если вы используете Maven, добавьте в ваш pom.xml:


<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

А если вы в команде Gradle, то в build.gradle:


implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.1'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-aop'

Настройка конфигурации

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

Создаём файл application.yml:


resilience4j:
  retry:
    instances:
      myRetryInstance:
        maxAttempts: 3
        waitDuration: 1s
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
        retryExceptions:
          - org.springframework.web.client.ResourceAccessException
          - java.util.concurrent.TimeoutException

  timelimiter:
    instances:
      myTimeoutInstance:
        timeoutDuration: 2s
        cancelRunningFuture: true

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

Разберём настройки подробнее:

  1. Настройки Retry:
    • maxAttempts: 3 — пробуем максимум 3 раза, потом сдаёмся
    • waitDuration: 1s — между попытками ждём секунду
    • enableExponentialBackoff: true — каждая следующая попытка ждёт дольше
    • exponentialBackoffMultiplier: 2 — время ожидания увеличивается вдвое
  2. Настройки Timeout:
    • timeoutDuration: 2s — две секунды на ответ, дольше не ждём
    • cancelRunningFuture: true — если время вышло, отменяем операцию

Создание сервиса с Retry и Timeout

Теперь создадим сервис, который будет использовать наши настройки. Представим, что мы делаем клиент для работы с внешним API платёжной системы:


@Service
public class PaymentService {
    private final RestTemplate restTemplate;
    private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);

    public PaymentService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Retry(name = "myRetryInstance")
    @TimeLimiter(name = "myTimeoutInstance")
    public CompletableFuture<String> processPayment(String orderId) {
        return CompletableFuture.supplyAsync(() -> {
            logger.info("Попытка обработать платёж для заказа: {}", orderId);
            return restTemplate.postForObject(
                "http://payment-service/api/payments",
                orderId,
                String.class
            );
        });
    }

    // Fallback метод, который будет вызван, если все попытки не удались
    public CompletableFuture<String> fallbackPayment(String orderId, Exception ex) {
        logger.error("Все попытки платежа для заказа {} не удались. Причина: {}",
                    orderId, ex.getMessage());
        return CompletableFuture.completedFuture(
            "Платёж временно невозможен. Попробуйте позже."
        );
    }
}

Создадим контроллер для нашего сервиса:


@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    private final PaymentService paymentService;

    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @PostMapping("/process")
    public CompletableFuture<String> processPayment(@RequestParam String orderId) {
        return paymentService.processPayment(orderId);
    }
}

Тестирование нашего решения

Для проверки работоспособности напишем тест:


@SpringBootTest
class PaymentServiceTest {
    @Autowired
    private PaymentService paymentService;

    @Test
    void testRetryAndTimeout() {
        CompletableFuture<String> result = paymentService.processPayment("test-order-123");

        assertThat(result)
            .isCompletedWithin(Duration.ofSeconds(10))
            .satisfies(response -> {
                assertThat(response)
                    .isNotNull()
                    .containsIgnoringCase("платёж");
            });
    }
}

Мониторинг работы Retry и Timeout

Благодаря Actuator мы можем следить за работой наших механизмов защиты. Откройте в браузере:

  • http://localhost:8080/actuator/retries
  • http://localhost:8080/actuator/timelimiterss

Вы увидите метрики по количеству успешных и неудачных попыток, среднему времени ответа и многое другое.


Типичные ошибки и как их избежать

  1. Слишком короткий таймаут Если ваш timeout меньше, чем среднее время ответа сервиса — вы получите много ложных срабатываний.
  2. Слишком частые повторы Маленький waitDuration может создать дополнительную нагрузку на и так проблемный сервис.
  3. Забытый fallback Всегда определяйте fallback метод — это ваша страховка на случай, когда все попытки исчерпаны.

Домашнее задание

  1. Добавьте Retry и Timeout в свой проект
  2. Поэкспериментируйте с разными настройками
  3. Реализуйте умный fallback, который будет возвращать кэшированные данные
  4. Настройте мониторинг через Actuator
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ