Gymterview
middle

Что такое Rate Limiting и как его реализовать?

Rate Limiting (ограничение частоты запросов) — механизм защиты API от злоупотреблений и перегрузки путём ограничения количества запросов от клиента за определённый период времени.

Основные алгоритмы

Алгоритм Описание
Fixed Window Подсчёт запросов в фиксированных временных интервалах (100 запросов в минуту)
Sliding Window Более точный, учитывает запросы за последние N секунд
Token Bucket Жетоны добавляются с постоянной скоростью. Каждый запрос забирает жетон. Нет жетонов — отказ
Leaky Bucket Запросы обрабатываются с постоянной скоростью, избыточные ставятся в очередь

HTTP-заголовки для Rate Limiting

Пример
HTTP/1.1 200 OK
X-RateLimit-Limit: 100           — максимум запросов
X-RateLimit-Remaining: 45        — осталось запросов
X-RateLimit-Reset: 1700000060    — время сброса (Unix timestamp)

HTTP/1.1 429 Too Many Requests
Retry-After: 30                  — через сколько секунд повторить
Реализация в Spring с помощью Bucket4j
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.7.0</version>
</dependency>
@Component
public class RateLimitFilter extends OncePerRequestFilter {

    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                     HttpServletResponse response,
                                     FilterChain filterChain)
            throws ServletException, IOException {

        String clientId = getClientId(request);
        Bucket bucket = buckets.computeIfAbsent(clientId, this::createBucket);

        ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
        response.setHeader("X-RateLimit-Limit", "100");
        response.setHeader("X-RateLimit-Remaining",
                           String.valueOf(probe.getRemainingTokens()));

        if (probe.isConsumed()) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.setHeader("Retry-After",
                String.valueOf(probe.getNanosToWaitForRefill() / 1_000_000_000));
            response.getWriter().write("{\"error\": \"Превышен лимит запросов\"}");
        }
    }

    private Bucket createBucket(String clientId) {
        return Bucket.builder()
            .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))
            .build();
    }

    private String getClientId(HttpServletRequest request) {
        String apiKey = request.getHeader("X-API-Key");
        return apiKey != null ? apiKey : request.getRemoteAddr();
    }
}
Реализация через Spring Cloud Gateway
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}

На собеседовании: нужно знать алгоритмы (Token Bucket самый популярный), HTTP-заголовки (429, Retry-After, X-RateLimit-*) и хотя бы один способ реализации. Частая ошибка — не упомянуть заголовок Retry-After для клиента.