Гарантированное получение данных через REST API: Подходы и библиотеки для Java

Подходы и примеры реализации механизма повторных вызовов (retry) в Java

261 открытий2К показов
Гарантированное получение данных через REST API: Подходы и библиотеки для Java

В крупном Java-проекте с множеством внешних и внутренних API-сервисов, одной из ключевых задач становится надежное получение данных, даже если происходят временные сбои. При активном росте системы и усложнении её архитектуры разработчики используют различные подходы к работе с веб-API — от RestTemplate до GRPC . Однако при таком разнообразии инструментов возникает необходимость внедрить универсальный механизм повторного вызова (retry), который позволяет автоматически повторять запросы в случае ошибок, увеличивая интервал между попытками.

В этой статье мы разберём основные подходы к работе с REST API в Java и рассмотрим популярные библиотеки для реализации retry-механизмов. Я также поделюсь собственным опытом внедрения повторных вызовов с экспоненциальным увеличением задержки между попытками.

Онлайн-курс «JAVA-разработчик» от EdMe.pro
  • бесплатно
  • набор еще идет
  • онлайн
tproger.ru

Стоит отметить, что далеко не во всех проектах требуется retry, но в нашей системе высокая надёжность — приоритетная задача. Мы работали в распределённой среде, где сервисы активно взаимодействовали друг с другом, а кратковременные сбои в сети могли приводить к сбоям в работе. В таких условиях механизм retry позволял минимизировать потери данных и повысить отказоустойчивость.

Какие ошибки можно решить с помощью retry? В первую очередь, это кратковременные сбои сети и таймауты при ожидании ответа от сервиса. Однако важно понимать, что если сервис перегружен, повторные запросы могут лишь усугубить проблему. В таких случаях необходимо оптимизировать сам сервис, а не полагаться исключительно на retry.

Далее мы рассмотрим, какие библиотеки позволяют реализовать retry в Java, какие стратегии повторных вызовов существуют и как их правильно применять в продакшн-системах.

Использование RetryTemplate с RestTemplate в Spring

RestTemplate — удобный HTTP-клиент в Spring Framework. Хотя он считается устаревшим, его всё ещё активно используют в существующих проектах. Чтобы повысить отказоустойчивость запросов, можно добавить механизм повторных попыток (retry) с использованием RetryTemplate из Spring Retry.

Пример: RestTemplate + RetryTemplate

			import org.springframework.retry.policy.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;

public class RestTemplateRetryExample {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        RetryTemplate retryTemplate = new RetryTemplate();

        // Настройка политики повторных вызовов (до 3 попыток)
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3);
        retryTemplate.setRetryPolicy(retryPolicy);

        // Настройка экспоненциальной задержки между попытками
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000); // Начальная задержка 1 секунда
        backOffPolicy.setMultiplier(1.1);       // Увеличение задержки в 1.1 раза
        backOffPolicy.setMaxInterval(10000);    // Максимальная задержка 10 секунд
        retryTemplate.setBackOffPolicy(backOffPolicy);

        String url = "https://example.my.server/api";

        try {
            String response = retryTemplate.execute(context -> 
                restTemplate.getForObject(url, String.class)
            );
            System.out.println("Response: " + response);
        } catch (Exception e) {
            System.err.println("Request failed: " + e.getMessage());
        }
    }
}

		

Использование WebClient с механизмом повторных попыток (retry)

WebClient — современный HTTP-клиент в Spring WebFlux, который поддерживает асинхронные запросы и реактивные потоки. В отличие от RestTemplate, он рассчитан на неблокирующую работу и уже имеет встроенный механизм повторных вызовов.

Пример: WebClient + retryWhen

			import org.springframework.web.reactive.function.client.WebClient;
import reactor.util.retry.Retry;

import java.time.Duration;

public class WebClientRetryExample {
    public static void main(String[] args) {
        String baseUrl = "https://example.my.server/api";

        WebClient webClient = WebClient.builder()
            .baseUrl(baseUrl)
            .build();

        webClient.get()
            .uri("/data")
            .retrieve()
            .bodyToMono(String.class)
            .retryWhen(
                Retry.backoff(3, Duration.ofSeconds(1))  // 3 попытки с экспоненциальной задержкой
                    .maxBackoff(Duration.ofSeconds(10))  // Максимальная задержка 10 секунд
            )
            .doOnError(e -> System.err.println("Request failed: " + e.getMessage()))
            .subscribe(response -> System.out.println("Response: " + response));
    }
}

		

Использование HttpClient (Java 11+) с механизмом повторных попыток

Начиная с Java 11, стандартная библиотека HttpClient предоставляет мощные возможности для отправки HTTP-запросов, включая асинхронную обработку и гибкие настройки повторных попыток (retry).

Пример: HttpClient с механизмом повторных вызовов

			import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpClientRetryExample {
    public static void main(String[] args) throws InterruptedException {
        HttpClient httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .build();

        String url = "https://example.my.server/api";
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .GET()
            .build();

        int maxRetries = 3; // Количество повторов
        int attempt = 0;

        while (attempt < maxRetries) {
            try {
                HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
                System.out.println("Response: " + response.body());
                break; // Успешный запрос, выходим из цикла
            } catch (Exception e) {
                attempt++;
                System.err.println("Attempt " + attempt + " failed: " + e.getMessage());

                if (attempt >= maxRetries) {
                    System.err.println("Max retries reached. Giving up.");
                    break;
                }

                long delay = (long) Math.pow(2, attempt) * 1000; // Экспоненциальная задержка
                Thread.sleep(delay);
            }
        }
    }
}

		

Реализация механизма повторных вызовов (Retry) в gRPC

В проекте, где активно используется gRPC наряду с REST API, также возникает необходимость реализовать retry. К счастью, gRPC предоставляет встроенные механизмы обработки повторных запросов, что делает его удобным для высокой надежности системы.

gRPC — высокопроизводительный RPC-фреймворк от Google, основанный на HTTP/2 и использующий бинарный формат данных (Protocol Buffers). Он поддерживает стриминг, асинхронные вызовы и механизмы автоматического повторного запроса при сбоях.

Онлайн-курс «JAVA-разработчик» от EdMe.pro
  • бесплатно
  • набор еще идет
  • онлайн
tproger.ru

В gRPC механизм retry можно настроить с помощью параметров retryPolicy, передаваемых в ServiceConfig:

			import io.grpc.*;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class GrpcRetryExample {
    public static void main(String[] args) throws InterruptedException {
        // Создаем канал с включенной поддержкой retry
        ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", 50005)
            .usePlaintext()
            .defaultServiceConfig(getServiceConfig()) // Конфигурация retry
            .enableRetry()  // Включение повторных попыток
            .build();

        MyServiceGrpc.MyServiceBlockingStub stub = MyServiceGrpc.newBlockingStub(channel);

        try {
            // Создаем gRPC-запрос
            MyServiceClass.MyRequest request = MyServiceClass.MyRequest.newBuilder()
                .setMessage("Hello gRPC")
                .build();

            // Отправляем запрос и обрабатываем ответ
            MyServiceClass.MyResponse response = stub.myMethod(request);
            System.out.println("Response: " + response.getMessage());

        } catch (Exception e) {
            System.err.println("RPC failed: " + e.getMessage());

        } finally {
            // Закрываем канал
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

    /**
     * Настройка retryPolicy для gRPC
     */
    private static Map<String, Object> getServiceConfig() {
        return Map.of(
            "methodConfig", List.of(
                Map.of(
                    "name", List.of(Map.of("service", "example.MyService")), // gRPC-сервис, для которого применяется конфигурация
                    "retryPolicy", Map.of(
                        "maxAttempts", 5, // Максимальное количество попыток
                        "initialBackoff", "0.1s", // Начальная задержка перед первой повторной попыткой
                        "maxBackoff", "5s", // Максимальная задержка между попытками
                        "backoffMultiplier", 2.0, // Увеличение задержки в 2 раза при каждой неудачной попытке
                        "retryableStatusCodes", List.of("UNAVAILABLE", "DEADLINE_EXCEEDED") // Ошибки, при которых срабатывает retry
                    )
                )
            )
        );
    }
}

		
Использование gRPC с retry повышает устойчивость микросервисов и делает систему более отказоустойчивой.

Мы рассмотрели несколько популярных подходов для гарантированного получения данных через REST API с использованием Java. Каждый из методов имеет свои преимущества и недостатки, а выбор подходящего решения зависит от конкретных требований проекта. Но стоит помнить, что при применении частых повторных вызовов нагрузка на сервис увеличивается, а ваш проект страдает.

Следите за новыми постами
Следите за новыми постами по любимым темам
261 открытий2К показов