Ограничение частоты запросов (Rate Limiting) в Java: обзор библиотек и инструментов
Rate limiting (ограничение частоты запросов) — критически важный механизм для защиты веб-сервисов, API и микросервисов от перегрузок, DDoS-атак и случайного злоупотребления. В Java существует несколько зрелых библиотек, реализующих различные алгоритмы: от простого счетчика до сложных распределенных систем. В этой статье мы рассмотрим три основных подхода: Guava RateLimiter, Bucket4j и Resilience4j, а также обсудим их применение.
1. Guava RateLimiter (простой и легковесный)
Библиотека Google Guava предоставляет класс RateLimiter, реализующий алгоритм Token Bucket (корзина токенов) в однопоточном режиме. Он идеально подходит для локального использования в одном JVM-процессе.
Принцип работы: Корзина наполняется токенами с определенной скоростью. Каждый запрос забирает один или несколько токенов. Если токенов недостаточно, поток блокируется или запрос отклоняется.
Пример использования Guava RateLimiter:
import com.google.common.util.concurrent.RateLimiter;
public class GuavaRateLimiterExample {
public static void main(String[] args) {
// Создаем RateLimiter на 10 запросов в секунду
RateLimiter rateLimiter = RateLimiter.create(10.0);
// Имитация входящих запросов
for (int i = 0; i < 20; i++) {
// tryAcquire() возвращает true, если токен получен
if (rateLimiter.tryAcquire()) {
System.out.println("Запрос " + i + " выполнен");
} else {
System.out.println("Запрос " + i + " отклонен (лимит превышен)");
}
}
}
}
Плюсы: Минимальная зависимость, простота, низкое потребление памяти.
Минусы: Не подходит для распределенных систем (не синхронизируется между инстансами).
2. Bucket4j (продвинутый Token Bucket с поддержкой Redis)
Bucket4j — это библиотека, реализующая алгоритм Token Bucket с расширенными возможностями: поддержка нескольких провайдеров для хранения состояния (in-memory, Redis, Hazelcast, Infinispan), что позволяет использовать её в распределенных сценариях.
Ключевые особенности:
- Поддержка greedy и interval-based режимов.
- Возможность задавать лимиты на разные временные окна (например, 100 запросов в минуту + 10 запросов в секунду).
- Интеграция с Jakarta EE / Spring Boot через готовые фильтры и аннотации.
Пример с in-memory хранилищем:
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import java.time.Duration;
public class Bucket4jExample {
public static void main(String[] args) {
// Лимит: 5 запросов в 10 секунд
Bandwidth limit = Bandwidth.classic(5, Refill.intervally(5, Duration.ofSeconds(10)));
Bucket bucket = Bucket.builder()
.addLimit(limit)
.build();
for (int i = 0; i < 10; i++) {
if (bucket.tryConsume(1)) {
System.out.println("Запрос " + i + " разрешен");
} else {
System.out.println("Запрос " + i + " заблокирован");
}
}
}
}
Плюсы: Гибкая настройка, распределенная поддержка, высокая производительность.
Минусы: Больше зависимостей, чем у Guava.
3. Resilience4j RateLimiter (для микросервисной архитектуры)
Resilience4j — это библиотека для обеспечения отказоустойчивости, в состав которой входит модуль RateLimiter. Она разработана с учетом принципов реактивного программирования и легко интегрируется с Spring Boot, Micrometer и другими инструментами.
Алгоритм: Использует Sliding Window (скользящее окно) на основе атомарных счетчиков. Поддерживает два режима: Atomic (блокирующий) и Semaphore (неблокирующий).
Пример с использованием Spring Boot:
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import java.time.Duration;
public class Resilience4jExample {
public static void main(String[] args) {
RateLim