Перейти к основному содержимому

Повторение неудачных запросов с помощью Spring Cloud Netflix Ribbon

· 7 мин. чтения

1. Обзор

Spring Cloud обеспечивает балансировку нагрузки на стороне клиента с помощью ленты Netflix . Механизм балансировки нагрузки ленты можно дополнить повторными попытками.

В этом уроке мы собираемся изучить этот механизм повторных попыток.

Во-первых, мы увидим, почему важно, чтобы наши приложения создавались с учетом этой функции. Затем мы создадим и настроим приложение с помощью Spring Cloud Netflix Ribbon, чтобы продемонстрировать механизм.

2. Мотивация

В облачном приложении служба обычно делает запросы к другим службам. Но в такой динамичной и нестабильной среде сети могут выйти из строя или службы могут быть временно недоступны.

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

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

Тем не менее, нам нужно следить за повторными попытками, поскольку они также могут привести к плохим ситуациям. Например, они могут увеличить задержку, что может быть нежелательно.

3. Настройка

Чтобы поэкспериментировать с механизмом повтора, нам нужны две службы Spring Boot. Во- первых, мы создадим службу погоды , которая будет отображать сегодняшнюю информацию о погоде через конечную точку REST.

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

3.1. Служба погоды

Давайте создадим очень простую службу погоды, которая иногда будет давать сбой с кодом состояния HTTP 503 (служба недоступна). Мы смоделируем этот прерывистый сбой, выбрав сбой, когда количество вызовов кратно настраиваемому свойству success.call.divisor :

@Value("${successful.call.divisor}")
private int divisor;
private int nrOfCalls = 0;

@GetMapping("/weather")
public ResponseEntity<String> weather() {
LOGGER.info("Providing today's weather information");
if (isServiceUnavailable()) {
return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
}
LOGGER.info("Today's a sunny day");
return new ResponseEntity<>("Today's a sunny day", HttpStatus.OK);
}

private boolean isServiceUnavailable() {
return ++nrOfCalls % divisor != 0;
}

Кроме того, чтобы помочь нам отслеживать количество повторных попыток обращения к службе, у нас есть регистратор сообщений внутри обработчика.

Позже мы собираемся настроить клиентскую службу для запуска механизма повторных попыток, когда служба погоды временно недоступна.

3.2. Служба поддержки клиентов

Наш второй сервис будет использовать ленту Spring Cloud Netflix.

Во-первых, давайте определим конфигурацию клиента ленты :

@Configuration
@RibbonClient(name = "weather-service", configuration = RibbonConfiguration.class)
public class WeatherClientRibbonConfiguration {

@LoadBalanced
@Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}

}

Наш HTTP-клиент помечен @LoadBalanced , что означает, что мы хотим, чтобы он балансировал нагрузку с помощью ленты.

Теперь мы добавим механизм проверки связи для определения доступности службы, а также стратегию балансировки нагрузки циклического перебора, определив класс RibbonConfiguration , включенный в аннотацию @RibbonClient выше:

public class RibbonConfiguration {

@Bean
public IPing ribbonPing() {
return new PingUrl();
}

@Bean
public IRule ribbonRule() {
return new RoundRobinRule();
}
}

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

Итак, давайте тоже добавим это все в файл application.yml :

weather-service:
ribbon:
eureka:
enabled: false
listOfServers: http://localhost:8021, http://localhost:8022

Наконец, давайте создадим контроллер и заставим его вызывать серверную службу:

@RestController
public class MyRestController {

@Autowired
private RestTemplate restTemplate;

@RequestMapping("/client/weather")
public String weather() {
String result = this.restTemplate.getForObject("http://weather-service/weather", String.class);
return "Weather Service Response: " + result;
}
}

4. Включение механизма повторных попыток

4.1. Настройка свойств application.yml

Нам нужно поместить свойства службы погоды в файл application.yml нашего клиентского приложения:

weather-service:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
retryableStatusCodes: 503, 408
OkToRetryOnAllOperations: true

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

  • MaxAutoRetries количество повторных попыток неудачного запроса на том же сервере (по умолчанию 0).
  • MaxAutoRetriesNextServer количество серверов для попытки, исключая первый (по умолчанию 0)
  • retryableStatusCodes список кодов состояния HTTP для повторной попытки
  • OkToRetryOnAllOperations когда для этого свойства установлено значение true, повторяются все типы HTTP-запросов, а не только запросы GET (по умолчанию).

Мы собираемся повторить неудачный запрос, когда клиентская служба получит код ответа 503 (служба недоступна) или 408 (тайм-аут запроса).

4.2. Требуемые зависимости

Лента Spring Cloud Netflix использует Spring Retry для повторения неудачных запросов.

Мы должны убедиться, что зависимость находится в пути к классам. В противном случае неудачные запросы не будут повторяться. Мы можем опустить версию, так как она управляется Spring Boot:

<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>

4.3. Повторите логику на практике

Наконец, давайте посмотрим на логику повторных попыток на практике.

По этой причине нам нужно два экземпляра нашей службы погоды, и мы запустим их на портах 8021 и 8022. Конечно, эти экземпляры должны соответствовать списку listOfServers, определенному в предыдущем разделе.

Кроме того, нам нужно настроить свойство success.call.divisor для каждого экземпляра, чтобы убедиться, что наши смоделированные службы не работают в разное время:

successful.call.divisor = 5 // instance 1
successful.call.divisor = 2 // instance 2

Далее также запустим клиентскую службу на порту 8080 и вызовем:

http://localhost:8080/client/weather

Взглянем на консоль погодного сервиса :

weather service instance 1:
Providing today's weather information
Providing today's weather information
Providing today's weather information
Providing today's weather information

weather service instance 2:
Providing today's weather information
Today's a sunny day

Итак, после нескольких попыток (4 на экземпляре 1 и 2 на экземпляре 2) мы получили правильный ответ.

5. Конфигурация политики отсрочки

Когда в сеть поступает больше данных, чем она может обработать, возникает перегрузка. Чтобы облегчить это, мы можем настроить политику отсрочки.

По умолчанию задержки между повторными попытками нет. Ниже Spring Cloud Ribbon используется объект Spring Retry NoBackOffPolicy , который ничего не делает. ``

Однако мы можем переопределить поведение по умолчанию, расширив класс RibbonLoadBalancedRetryFactory :

@Component
private class CustomRibbonLoadBalancedRetryFactory
extends RibbonLoadBalancedRetryFactory {

public CustomRibbonLoadBalancedRetryFactory(
SpringClientFactory clientFactory) {
super(clientFactory);
}

@Override
public BackOffPolicy createBackOffPolicy(String service) {
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000);
return fixedBackOffPolicy;
}
}

Класс FixedBackOffPolicy обеспечивает фиксированную задержку между повторными попытками. Если мы не устанавливаем период отсрочки, значение по умолчанию равно 1 секунде.

В качестве альтернативы мы можем настроить ExponentialBackOffPolicy или ExponentialRandomBackOffPolicy :

@Override
public BackOffPolicy createBackOffPolicy(String service) {
ExponentialBackOffPolicy exponentialBackOffPolicy =
new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1000);
exponentialBackOffPolicy.setMultiplier(2);
exponentialBackOffPolicy.setMaxInterval(10000);
return exponentialBackOffPolicy;
}

Здесь начальная задержка между попытками составляет 1 секунду. Затем задержка удваивается для каждой последующей попытки, не превышающей 10 секунд: 1000 мс, 2000 мс, 4000 мс, 8000 мс, 10000 мс, 10000 мс…

Кроме того, ExponentialRandomBackOffPolicy добавляет случайное значение к каждому периоду ожидания без превышения следующего значения. Таким образом, это может дать 1500 мс, 3400 мс, 6200 мс, 9800 мс, 10000 мс, 10000 мс…

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

6. Заключение

В этой статье мы узнали, как повторить неудачные запросы в наших приложениях Spring Cloud с помощью ленты Spring Cloud Netflix. Мы также обсудили преимущества, которые дает этот механизм.

Далее мы продемонстрировали, как работает логика повторных попыток в приложении REST, поддерживаемом двумя службами Spring Boot. Spring Cloud Netflix Ribbon делает это возможным благодаря использованию библиотеки Spring Retry.

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

Как всегда, исходный код этого руководства доступен на GitHub .