Rate Limiting в Java: от теории до практической реализации
Rate Limiting (ограничение скорости запросов) — один из ключевых механизмов защиты и управления нагрузкой в современных распределённых системах. В Java эта задача решается как на уровне встроенных средств, так и с помощью специализированных библиотек. В этой статье мы разберём основные алгоритмы, популярные библиотеки и приведём рабочие примеры кода.
Зачем нужно Rate Limiting?
Rate Limiting позволяет:
- Защитить API от злоупотреблений (DDoS, перебор паролей)
- Равномерно распределять нагрузку на бэкенд
- Соблюдать соглашения об уровне обслуживания (SLA)
- Предотвращать превышение квот внешних сервисов
Основные алгоритмы Rate Limiting
Перед выбором библиотеки важно понимать, какие алгоритмы лежат в основе:
1. Token Bucket (Bucket4j, Guava)
Классический алгоритм. В "корзину" с определённой скоростью добавляются токены. Каждый запрос забирает один или несколько токенов. Если токенов нет — запрос отклоняется.
// Пример на псевдокоде
class TokenBucket {
private int tokens;
private long lastRefillTime;
private final int capacity;
private final double refillRate; // токенов в секунду
synchronized boolean tryConsume() {
refill();
if (tokens > 0) {
tokens--;
return true;
}
return false;
}
}
2. Sliding Window Log
Хранит временные метки последних запросов. При каждом новом запросе удаляет метки старше окна и проверяет, не превышен ли лимит. Точен, но требует памяти под хранение меток.
3. Fixed Window Counter
Простой счётчик на фиксированный интервал (например, 100 запросов в минуту). Сбрасывается каждую минуту. Уязвим для "пиковых" нагрузок на границах окна.
Популярные Java-библиотеки для Rate Limiting
1. Bucket4j
Bucket4j — зрелая библиотека, полностью реализующая алгоритм Token Bucket. Поддерживает распределённые сценарии через Redis, Hazelcast, JCache. Имеет Java 8+ API.
Установка (Maven):
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.7.0</version>
</dependency>
Пример использования:
import io.github.bucket4j.*;
import java.time.Duration;
public class Bucket4jExample {
public static void main(String[] args) {
// Создаём корзину: 10 токенов, пополнение 1 токен в секунду
Bandwidth limit = Bandwidth.simple(10, Duration.ofSeconds(1));
Bucket bucket = Bucket4j.builder().addLimit(limit).build();
for (int i = 0; i < 15; i++) {
if (bucket.tryConsume(1)) {
System.out.println("Запрос #" + i + " выполнен");
} else {
System.out.println("Запрос #" + i + " отклонён (лимит превышен)");
}
}
}
}
2. Resilience4j RateLimiter
Часть популярного фреймворка для отказоустойчивости. Работает на основе SemaphoreBasedRateLimiter или AtomicRateLimiter. Легко интегрируется с Spring Boot.
Пример конфигурации:
import io.github.resilience4j.ratelimiter.*;
import java.time.Duration;
public class Resilience4jExample {
public static void main(String[] args) {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(5) // 5 запросов
.limitRefreshPeriod(Duration.ofSeconds(1)) // каждую секунду
.timeoutDuration(Duration.ofMillis(100))
.build();
RateLimiter rateLimiter = RateLimiter.of("myService", config);
for (int i = 0; i < 8; i++) {
Runnable task = RateLimiter.decorateRunnable(rateLimiter, () -> {
System.out.println("Запрос #" + i + " выполнен");
});
try {
task.run();
} catch (Exception e) {
System.out.println("Запрос #" + i + " отклонён: " + e.getMessage());
}
}
}
}
3. Google Guava RateLimiter
Встроенный в библиотеку Guava (Google). Простой, но не поддерживает распределённые сценарии. Работает на алгоритме Token Bucket с "предварительным потреблением" (burst).
import com.google.common.util.concurrent.RateLimiter;
public class GuavaRateLimiterExample {
public static void main(String[] args) {
// 2 запроса в секунду
RateLimiter rateLimiter = Rate