Часть серии статей Resilience4J. Если вы еще не читали другие мои статьи, перейдите по следующим ссылкам:
1. Шаблон прерывателя цепи в Spring Boot

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

Вот несколько преимуществ использования ограничителей скорости:

  1. Предотвратите нехватку ресурсов в результате злонамеренных атак (например, DoS или DDoS), преднамеренных или непреднамеренных, заблокировав избыточные вызовы.
  2. Снизить стоимость. Сокращение дополнительных запросов означает уменьшение количества серверов и сосредоточение ресурсов на важных API.
  3. Не допускайте перегрузки серверов. Ограничители скорости могут отфильтровывать дополнительные запросы, вызванные ботами или неправильным поведением пользователей.

Что такое Resilience4j-Ratelimiter?

Resilience4J Ratelimiter предоставляет простой способ настройки и применения стратегий ограничения скорости к определенным частям приложения. Разработчики просто устанавливают ограничения на количество запросов (limit-for-period) в определенное время (limit-refresh-period). Это помогает разработчикам более активно управлять трафиком, предотвращать перегрузку служб и обеспечивать стабильность и надежность своих приложений.

Теперь давайте подробнее рассмотрим цикл в Resilience4j-RateLimiter:

  1. Период прогрева: при запуске приложения или после сброса может быть период прогрева, в течение которого ограничитель скорости постепенно увеличивает разрешенную частоту запросов. Это делается для предотвращения внезапного всплеска трафика сразу после запуска, который потенциально может перегрузить систему.
  2. Установившееся состояние. По окончании периода прогрева ограничитель скорости переходит в устойчивое состояние. На этом этапе ограничитель скорости разрешает прохождение запросов на основе настроенных ограничений скорости. Например, если вы установили ограничение в 100 запросов в минуту, ограничитель скорости будет разрешать примерно один запрос каждые 0,6 секунды.
  3. Ограничение превышено: если скорость входящих запросов превышает настроенные пределы, ограничитель скорости начинает немедленно отклонять избыточные запросы. Это помогает предотвратить перегрузку системы.
  4. Пополнение токенов: ограничитель скорости постоянно пополняет «токены» со скоростью, соответствующей настроенным ограничениям. Каждый разрешенный запрос потребляет один токен. Если система не полностью использует разрешенную скорость, неиспользуемые токены накапливаются, что позволяет время от времени получать всплески запросов.
  5. Период восстановления: если ограничитель скорости отклонял запросы из-за превышения ограничений скорости, может быть период восстановления, в течение которого ограничитель скорости снова постепенно увеличивает разрешенную скорость запросов. Это делается для предотвращения внезапного всплеска трафика после ослабления ограничений.

Чтобы узнать больше о Resilience4j-RateLimiter, обратитесь к официальной документации: Ratelimiter

Это подводит итог концепции шаблона ограничения скорости. Довольно просто, не так ли? 😃 Двигаясь дальше, давайте углубимся в практическую реализацию шаблона ограничения скорости в Spring Boot.

Демонстрация микросервисов

В нашей демонстрации есть 2 службы с именами payment-service и payment-processor.

Сценарий

  1. Платежная служба обрабатывает входящие платежные запросы от покупателей и направляет их в платежную систему для обработки.
  2. Платежный процессор обрабатывает и отправляет результаты.

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

Платежной системы

Давайте сначала создадим обработчик платежей, так как это зависимая служба.

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

Предпосылки

  • Ява 17
  • Мейвен Обертка
  • Весенняя загрузка 3+

Определение зависимостей

Создайте проект как проект Spring Boot с зависимостями, указанными в файле POM ниже. Я назвал его платежный процессор.

https://github.com/buingoctruong/rate-limiting-pattern-spring-boot/blob/master/payment-processor/pom.xml

Свойства

server:
  port: 1010
spring:
  application:
    name: payment-processor

Сервис

public interface PaymentProcessorService {
    String processPayment(String paymentInfo);
}
@Service
public class PaymentProcessorServiceImpl implements PaymentProcessorService {
    @Override
    public String processPayment(String paymentInfo) {
        // Simulated logic to process payment
        return "Payment processed: " + paymentInfo;
    }
}

Контроллер

@RestController
@RequestMapping("/api/v1/processor-payment")
@RequiredArgsConstructor
public class PaymentProcessorController {
    private final PaymentProcessorService paymentProcessorService;
    @PostMapping
    public String processPayment(@RequestBody String paymentInfo) {
        return paymentProcessorService.processPayment(paymentInfo);
    }
}

Мы закончили создание платежного процессора. Запустите и перейдите по ссылке http://localhost:1010/api/v1/processor-payment, используя тело запроса Информация о платеже. Ожидаемый ответ должен быть таким, как показано ниже.

Payment processed: Payment Information

Платежный сервис

Когда дело доходит до платежного сервиса, наиболее интересной частью может быть настройка ограничителя скорости и отслеживание его состояния с помощью Actuator. Я постараюсь дать простое объяснение, так что давайте начнем!

Предпосылки

  • Ява 17
  • Мейвен Обертка
  • Весенняя загрузка 3+
  • устойчивость4j
  • Актуатор

Определение зависимостей

Создайте проект как проект Spring Boot с зависимостями, указанными в файле POM ниже. Я назвал его платежный сервис.

https://github.com/buingoctruong/rate-limiting-pattern-spring-boot/blob/master/payment-service/pom.xml

Модель

public interface Type {
}
@Data
public class Success implements Type {
    private final String msg;
}
@Data
public class Failure implements Type {
    private final String msg;
}

Сервис

Вот место, где написана всякая логика.

Сложность заключается в том, как вызвать внешний API. К счастью, в Spring есть RestTemplate, который может помочь нам в этом.

RestTemplate — это центральный класс Spring, используемый для использования веб-служб для всех методов HTTP. (Помните, что нам нужно создать RestTemplateBean, см. раздел Настройка ниже).

public interface PaymentService {
    Type submitPayment(String paymentInfo);
}
@Service
@RequiredArgsConstructor
public class PaymentServiceImpl implements PaymentService {
    private final RestTemplate restTemplate;
    private static final String SERVICE_NAME = "payment-service";
    private static final String PAYMENT_PROCESSOR_URL = "http://localhost:1010/api/v1/processor-payment";
    @RateLimiter(name = SERVICE_NAME, fallbackMethod = "fallbackMethod")
    public Type submitPayment(String paymentInfo) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(paymentInfo, headers);
        ResponseEntity<String> response = restTemplate.exchange(PAYMENT_PROCESSOR_URL,
                HttpMethod.POST, entity, String.class);
        Success success = new Success(response.getBody());
        return success;
    }

    private Type fallbackMethod(RequestNotPermitted requestNotPermitted) {
        return new Failure("Payment service does not permit further calls");
    }
}

Как вы заметили, мы аннотируем метод @RateLimiter». Атрибут name назначается как payment-service, что означает, что для этого метода применяется каждая конфигурация экземпляра payment-service. . (вы можете увидеть подробную информацию о конфигурации в разделе Свойства ниже). Затем мы также используем атрибут fallbackMethod с целью вызвать метод резервного копирования в случае превышения ограничения скорости и возникновения исключения. Мы должны быть осторожны, чтобы оба метода возвращали один и тот же тип данных. Теперь вы можете понять, почему я использую Интерфейс типов для реализации обоих классов модели.

Настройка

@Configuration
public class RestConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Свойства

server:
  port: 9090
spring:
  application:
    name: payment-service
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: health
  health:
    ratelimiters:
      enabled: true
resilience4j:
  ratelimiter:
    instances:
      payment-service:
        limit-for-period: 5
        limit-refresh-period: 15s
        timeout-duration: 10s
        register-health-indicator: true

Позвольте мне представить краткое введение в конфигурации resilience4j-ratelimiter.

  • limit-for-period: количество разрешенных запросов в течение одного «лимитного периода обновления».
  • limit-refresh-period: указывает продолжительность, по истечении которой «limit-for-period» будет сброшен.
  • timeout-duration: устанавливает максимальное время ожидания для ограничителя скорости, чтобы разрешить последующие запросы.

Мы закончили создание платежного сервиса. Запустить последовательно платеж-процессор, а затем платеж-сервис, получить доступ по ссылке http://localhost:9090/api/v1/payment-service с помощью тела запроса Информация о платеже для одного из ожидаемые ответы ниже.

// Successful Response
{
    "msg": "Payment processed: Payment Information"
}
// Rate limit exceeded response when requests exceed limit
{
    "msg": "Payment service does not permit further calls"
}

Игра с ограничением скорости

Обе службы уже запущены, перейдите по ссылке http://localhost:9090/actuator/health, чтобы просмотреть сведения об ограничителе скорости.

{
  "rateLimiters": {
    "status": "UP",  // "UP" suggesting that the rate limiters are functioning properly
    "details": {
      "payment-service": {
        "status": "UP",
        "details": {
          "availablePermissions": 5,  // number of allowed requests within the specified rate limit
          "numberOfWaitingThreads": 0 // number of threads waiting for their requests to be processed
        }
      }
    }
  }
}

Нажмите API 3 раза http://localhost:9090/api/v1/payment-service, используя тело запроса Информация о платеже, затем обновите ссылку активатора http://localhost:9090/actuator/health. увидеть изменение.

{
  "rateLimiters": {
    "status": "UP",
    "details": {
      "payment-service": {
        "status": "UP",
        "details": {
          "availablePermissions": 2, // number of allowed requests now is 2
          "numberOfWaitingThreads": 0
      }
    }
  }
}

Подождите 15 секунд (возможно меньше, если период начался до доступа к API), затем обновите ссылку актуатора http://localhost:9090/actuator/health, и мы будем наблюдать разрешенные запросы сброс до 5.

{
  "rateLimiters": {
    "status": "UP",
    "details": {
      "payment-service": {
        "status": "UP",
        "details": {
          "availablePermissions": 5,
          "numberOfWaitingThreads": 0
      }
    }
  }
}

Нажмите API 6 раз http://localhost:9090/api/v1/payment-service, используя тело запроса Информация о платеже. Шестой запрос будет задержан на 5 секунд из-за превышения лимитов. Во время ожидания обновите http://localhost:9090/actuator/health для получения следующих сведений.

{
  "rateLimiters": {
    "status": "UNKNOWN",
    "details": {
      "payment-service": {
        "status": "RATE_LIMITED",
        "details": {
          "availablePermissions": 0,
          "numberOfWaitingThreads": 1
      }
    }
  }
}

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

Надеюсь, вы сможете найти что-то полезное!

Готовый исходный код можно найти в этом репозитории GitHub: https://github.com/buingoctruong/rate-limiting-pattern-spring-boot.

Я хотел бы услышать ваши мысли!

Спасибо за чтение, и до свидания!