Skip to content
IRC-CodingIRC-Coding
Rate LimitingRedisРеализацияDistributed SystemsAPI GatewayToken Bucket

Реализация API Rate Limiting для распределённых систем

API Rate Limiting: алгоритмы, Redis, заголовки, примеры кода, обработка ошибок и лучшие практики.

S

schutzgeist

6 min read
Реализация API Rate Limiting для распределённых систем

Реализация API Rate Limiting

Rate Limiting для API в распределённых системах требует консистентного управления состоянием, правильного выбора алгоритма и чёткой коммуникации с клиентами.

Краткое описание

API Rate Limiting описывает техническую реализацию ограничений на количество запросов к API. В распределённых системах лимит должен соблюдаться согласованно на всех инстансах, что требует централизованного хранилища вроде Redis или базы данных. Основные алгоритмы: Fixed Window, Sliding Window, Token Bucket и Leaky Bucket. Каждый имеет разные характеристики по справедливости, поведению при всплесках нагрузки и сложности реализации. При разработке нужно учитывать идентификацию клиента, атомарные операции, заголовки статуса, обработку ошибок с кодом 429 и Retry-After, выбор оптимальных лимитов. Rate Limiting стоит конфигурировать не только в коде приложения, но и на уровне API Gateway или Load Balancer для защиты в несколько слоёв.

Ключевые компоненты

Идентификация клиента

Rate Limiting требует уникальной идентификации клиента. Для этого используют API ключи, ID пользователя, IP-адреса или комбинации нескольких факторов. Для аутентифицированных клиентов ID пользователя обычно наилучший выбор, так как IP-адреса меняются и несколько клиентов могут находиться за одним адресом.

Реализация Fixed Window

Fixed Window считает запросы в фиксированных временных окнах. В Redis можно хранить счётчик для каждого окна и клиента. Счётчик создаётся при первом запросе в окне и истекает по прошествии времени. Если счётчик превышает лимит, запрос отклоняется.

Реализация Sliding Window

Sliding Window справедливее Fixed Window, но сложнее в реализации. Один из вариантов: Sliding Window Log, при котором для каждого клиента хранится список временных меток последних запросов. Запросы вне окна удаляются. Если количество оставшихся запросов больше лимита, новый запрос отклоняется.

Реализация Token Bucket

Token Bucket часто используемый алгоритм. Корзина имеет максимальную ёмкость и заполняется токенами с постоянной скоростью. Каждый запрос потребляет один токен. Если корзина пуста, запрос отклоняется или задерживается. Redis хорошо подходит для Token Bucket с использованием Lua-скриптов для атомарных операций.

Реализация Leaky Bucket

Leaky Bucket обрабатывает запросы с постоянной скоростью. Запросы буферизуются в очереди и обрабатываются равномерно. Алгоритм хорош для плавной обработки, но может привести к задержкам и выбросу запросов при переполнении очереди.

Redis для распределённого Rate Limiting

Redis предлагает быстрые атомарные операции и TTL, что делает его идеальным для Rate Limiting. Lua-скрипты позволяют выполнять несколько команд атомарно. Redis Cluster или Redis Sentinel обеспечивают высокую доступность. При очень высоких требованиях Redis может стать узким местом, поэтому дополнительно можно использовать кэширование или локальные proxy-кэши.

Атомарные операции

Rate Limiting должен быть атомарным, чтобы избежать race conditions. Если несколько запросов одновременно проверяют наличие оставшегося лимита, нельзя допустить его превышение. Lua-скрипты в Redis или транзакции в базах данных гарантируют атомарность.

Заголовки для отображения статуса

Клиентов необходимо информировать о их текущем статусе. Заголовки X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset широко распространены. Альтернативно можно использовать RFC-RateLimit схему. Эти заголовки позволяют клиентам корректировать частоту своих запросов.

Обработка ошибок с кодом 429

Когда лимит превышен, API отвечает 429 Too Many Requests. Заголовок Retry-After должен указать, когда клиент может повторить попытку. Ответ об ошибке должен следовать формату RFC 7807 Problem Details и не содержать внутренних деталей.

Лимиты по эндпоинтам и по пользователям

Разные эндпоинты имеют разные затраты. Операции записи и дорогие запросы должны иметь более строгие лимиты, чем простые операции чтения. Аналогично, разные группы пользователей могут получить разные лимиты, например бесплатные и платные клиенты.

Rate Limiting на уровне API Gateway

API Gateway вроде Kong, Nginx или AWS API Gateway предлагают встроенное Rate Limiting. Они защищают API до того, как запросы достигнут бэкенда. Gateway особенно эффективны для глобальных лимитов и простых правил, в то время как код приложения может реализовать более сложные, специфичные для пользователя правила.

Практический пример

Node.js сервис реализует Token Bucket Rate Limiting с использованием Redis.

const redis = require('redis');
const client = redis.createClient();

const LIMIT = 100;
const WINDOW_SECONDS = 60;

async function isAllowed(clientId) {
  const key = `rate_limit:${clientId}`;
  const lua = `
    local key = KEYS[1]
    local limit = tonumber(ARGV[1])
    local window = tonumber(ARGV[2])
    local current = redis.call('GET', key)
    if current == false then
      current = 0
    end
    current = tonumber(current)
    if current >= limit then
      return 0
    end
    redis.call('INCR', key)
    if current == 0 then
      redis.call('EXPIRE', key, window)
    end
    return 1
  `;

  const result = await client.eval(lua, { keys: [key], arguments: [String(LIMIT), String(WINDOW_SECONDS)] });
  return result === 1;
}

Middleware в Express:

async function rateLimit(req, res, next) {
  const clientId = req.user?.id || req.ip;
  const allowed = await isAllowed(clientId);
  if (!allowed) {
    res.set('Retry-After', String(WINDOW_SECONDS));
    return res.status(429).json({
      type: 'https://api.example.com/problems/rate-limit-exceeded',
      title: 'Rate Limit превышен',
      status: 429,
      detail: `Вы превысили лимит в ${LIMIT} запросов на ${WINDOW_SECONDS} секунд.`
    });
  }
  next();
}

Такой подход атомарен, работает на нескольких серверных инстансах и ясно сообщает клиентам о лимитах.

FAQ: Реализация API Rate Limiting

1. Как идентифицировать клиента для Rate Limiting?

Клиентов обычно идентифицируют по API ключам, ID пользователя или IP-адресам. Для аутентифицированных API ID пользователя обычно самый надёжный выбор.

2. Почему Redis подходит для Rate Limiting?

Redis быстр, предлагает атомарные операции и TTL, централизованно доступен в распределённых системах. Lua-скрипты позволяют выполнять сложные атомарные операции подсчёта.

3. Что такое Token Bucket в реализации?

Token Bucket заполняется токенами с постоянной скоростью. Каждый запрос потребляет один токен. Если корзина пуста, запрос отклоняется. Redis и Lua-скрипты обеспечивают атомарную реализацию.

4. Что такое атомарное Rate Limiting?

Атомарное Rate Limiting гарантирует, что проверка и обновление лимита выполняются как одна неделимая операция. Это предотвращает race conditions и превышение лимита при параллельных запросах.

5. Что такое Sliding Window Log?

Sliding Window Log сохраняет временные метки каждого запроса клиента. Запросы вне окна удаляются. Если количество оставшихся запросов больше лимита, новый запрос отклоняется.

6. Что такое Fixed Window?

Fixed Window считает запросы в фиксированных временных окнах. Просто реализуется, но может привести к всплескам на границах окон.

7. Что такое Leaky Bucket?

Leaky Bucket помещает запросы в очередь и обрабатывает их с постоянной скоростью. Очень хорошо сглаживает трафик, но может привести к задержкам.

8. Что такое race condition при Rate Limiting?

Race condition возникает, когда несколько параллельных запросов одновременно проверяют и обновляют лимит. Без атомарных операций лимит может быть превышен.

9. Какие заголовки нужно устанавливать при Rate Limiting?

Заголовки X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset и Retry-After информируют клиентов о их лимите и следующем допустимом времени.

10. Что такое 429 Too Many Requests?

429 Too Many Requests это HTTP-код статуса, указывающий, что клиент превысил Rate Limit. Вместе с Retry-After он позволяет справедливо повторять попытки.

11. Должен ли Rate Limiting находиться на Gateway или в коде?

Оба подхода полезны. Gateway подходит для глобальных и простых лимитов, код приложения для более сложных правил, специфичных для пользователей или эндпоинтов.

12. Что такое Per-Endpoint лимиты?

Per-Endpoint лимиты применяются к отдельным эндпоинтам. Операции записи и дорогие запросы часто получают более строгие лимиты, чем простые операции чтения.

13. Что такое узкое место Rate Limiting?

Узкое место возникает, когда централизованное хранилище Rate Limiting, например Redis, перегружается. Решения: кэширование, локальные proxy-кэши или оптимизированная структура данных.

14. Какая хорошая стратегия Rate Limiting?

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

15. Какая польза от Retry-After?

Retry-After сообщает клиенту, когда он может повторить запрос. Это снижает ненужные запросы, экономит ресурсы API и повышает успешность повторных попыток.

Продолжение обучения по API

Следующая статья в цикле про API посвящена Rate Limiting и Throttling для APIs — стратегиям и алгоритмам rate limiting, а также различиям между rate limiting и throttling.

Источники

  1. https://www.rfc-editor.org/rfc/rfc6585
  2. https://redis.io/docs/manual/programmability/eval-intro/
  3. https://www.konghq.com/kong

Книги по разработке API

Если хочешь углубить знания о rate limiting, распределённых системах и архитектуре API, рекомендуем следующие книги:

Keine Bücher für Kategorie "api-development" gefunden.

Назад к блогу
Share:

Nächster Artikel in Разработка API

Weiterlesen
SOAP vs REST vs GraphQL: сравнение API

Похожие статьи