Часть серии статей Resilience4J. Если вы еще не читали другие мои статьи, перейдите по следующим ссылкам:
1. Шаблон прерывателя цепи в Spring Boot
Вас когда-нибудь интересовали ограничители скорости в мире HTTP? Думайте о них как о регулировщиках дорожного движения. Они управляют скоростью трафика от клиентов или служб, ограничивая количество разрешенных запросов в течение определенного периода. Если количество запросов превышает установленный предел, определенный ограничителем скорости, все лишние вызовы блокируются.
Вот несколько преимуществ использования ограничителей скорости:
- Предотвратите нехватку ресурсов в результате злонамеренных атак (например, DoS или DDoS), преднамеренных или непреднамеренных, заблокировав избыточные вызовы.
- Снизить стоимость. Сокращение дополнительных запросов означает уменьшение количества серверов и сосредоточение ресурсов на важных API.
- Не допускайте перегрузки серверов. Ограничители скорости могут отфильтровывать дополнительные запросы, вызванные ботами или неправильным поведением пользователей.
Что такое Resilience4j-Ratelimiter?
Resilience4J Ratelimiter предоставляет простой способ настройки и применения стратегий ограничения скорости к определенным частям приложения. Разработчики просто устанавливают ограничения на количество запросов (limit-for-period) в определенное время (limit-refresh-period). Это помогает разработчикам более активно управлять трафиком, предотвращать перегрузку служб и обеспечивать стабильность и надежность своих приложений.
Теперь давайте подробнее рассмотрим цикл в Resilience4j-RateLimiter:
- Период прогрева: при запуске приложения или после сброса может быть период прогрева, в течение которого ограничитель скорости постепенно увеличивает разрешенную частоту запросов. Это делается для предотвращения внезапного всплеска трафика сразу после запуска, который потенциально может перегрузить систему.
- Установившееся состояние. По окончании периода прогрева ограничитель скорости переходит в устойчивое состояние. На этом этапе ограничитель скорости разрешает прохождение запросов на основе настроенных ограничений скорости. Например, если вы установили ограничение в 100 запросов в минуту, ограничитель скорости будет разрешать примерно один запрос каждые 0,6 секунды.
- Ограничение превышено: если скорость входящих запросов превышает настроенные пределы, ограничитель скорости начинает немедленно отклонять избыточные запросы. Это помогает предотвратить перегрузку системы.
- Пополнение токенов: ограничитель скорости постоянно пополняет «токены» со скоростью, соответствующей настроенным ограничениям. Каждый разрешенный запрос потребляет один токен. Если система не полностью использует разрешенную скорость, неиспользуемые токены накапливаются, что позволяет время от времени получать всплески запросов.
- Период восстановления: если ограничитель скорости отклонял запросы из-за превышения ограничений скорости, может быть период восстановления, в течение которого ограничитель скорости снова постепенно увеличивает разрешенную скорость запросов. Это делается для предотвращения внезапного всплеска трафика после ослабления ограничений.
Чтобы узнать больше о Resilience4j-RateLimiter, обратитесь к официальной документации: Ratelimiter
Это подводит итог концепции шаблона ограничения скорости. Довольно просто, не так ли? 😃 Двигаясь дальше, давайте углубимся в практическую реализацию шаблона ограничения скорости в Spring Boot.
Демонстрация микросервисов
В нашей демонстрации есть 2 службы с именами payment-service и payment-processor.
Сценарий
- Платежная служба обрабатывает входящие платежные запросы от покупателей и направляет их в платежную систему для обработки.
- Платежный процессор обрабатывает и отправляет результаты.
Мы внедрим ограничение скорости в платежном сервисе, чтобы контролировать скорость входящих платежных запросов.
Платежной системы
Давайте сначала создадим обработчик платежей, так как это зависимая служба.
Пожалуйста, имейте в виду, что реальный платежный процессор может иметь гораздо более сложную реализацию. В целях нашей демонстрации я упростил его для отображения сообщений об успешном завершении.
Предпосылки
- Ява 17
- Мейвен Обертка
- Весенняя загрузка 3+
Определение зависимостей
Создайте проект как проект Spring Boot с зависимостями, указанными в файле POM ниже. Я назвал его платежный процессор.
Свойства
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 ниже. Я назвал его платежный сервис.
Модель
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.
Я хотел бы услышать ваши мысли!
Спасибо за чтение, и до свидания!