Получить доступ

Спроектируйте распределённый rate limiter для публичного API.

SeniorЗадачи на проектирование
Ещё спрашивают так:
  • Как ограничить число запросов клиента, если серверов много?
  • Как реализовать rate limiting на Redis?
Коротко

Состояние лимита — в общем быстром хранилище (Redis), проверка и списание — атомарно одним скриптом (Lua) или командой. Лимитер — в шлюзе или сайдкаре, до бизнес-логики.

  • Token bucket в Redis: два поля (жетоны, время) и Lua-скрипт на проверку
  • Задержка: поход в Redis на каждый запрос — 0,2–1 мс; для огромных потоков — локальные квоты с периодической синхронизацией
  • При недоступности Redis — осознанный выбор fail-open или fail-closed

А ещё Ответ 429 с Retry-After и заголовками RateLimit-*; разные лимиты по тарифам

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

Rate limiter: token bucket и окна

Подробно

Требования: лимиты по API-ключу (тариф: 100 запросов/с, 1 млн в день), по IP для анонимных, по эндпоинту для тяжёлых операций; десятки экземпляров шлюза; добавка к задержке — не больше миллисекунды; точность ±несколько процентов допустима.

Почему не локально. Если у каждого из 20 экземпляров свой счётчик, клиент получит до 20× лимита (балансировщик раскидывает запросы). Делить лимит на число экземпляров — неточно при неравномерной балансировке и автомасштабировании.

Централизованное состояние в Redis:

  • фиксированное окно — INCR key:{client}:{minute} + EXPIRE: одна команда, но всплеск на стыке окон;
  • скользящий журнал — sorted set с метками времени, ZREMRANGEBYSCORE + ZCARD + ZADD в одном скрипте: точно, но память на каждый запрос;
  • token bucket — хэш {tokens, ts} и Lua-скрипт: пополнить по прошедшему времени, проверить, списать, записать — атомарно, потому что Redis исполняет скрипт целиком. Время берём из Redis (TIME), а не с шлюзов — их часы расходятся.

Масштаб Redis: ключи по клиентам шардируются в Redis Cluster естественно; горячий клиент — один ключ, но один Redis держит сотни тысяч операций в секунду. Реплики не помогают (запись на каждый запрос).

Снижение задержки и нагрузки:

  • локальные квоты — шлюз берёт у Redis пачку жетонов (например, 10% лимита) и тратит локально, синхронизируясь при исчерпании. Точность чуть ниже, походов в Redis в разы меньше;
  • предварительная проверка локальным приблизительным лимитером, к Redis — только близко к порогу.

Отказ хранилища: fail-open (пропускать — сервис доступен, но без защиты) или fail-closed (отказывать — защищено, но недоступно); часто — fail-open с грубым локальным лимитом на экземпляр как страховкой. Таймаут на поход в Redis — единицы миллисекунд.

Поведение для клиента: 429 Too Many Requests, Retry-After, RateLimit-Limit/Remaining/Reset; лимиты по тарифу из конфигурации, которую можно менять без выкатки; отдельные лимиты на тяжёлые операции (стоимость в жетонах).

Наблюдаемость: метрики отказов по клиентам и правилам — чтобы отличить атаку от ошибки конфигурации, которая режет честных клиентов.

Пример

-- KEYS[1] = bucket:{api_key}; ARGV = rate, burst, cost
local t = redis.call('TIME')
local now = t[1] + t[2] / 1e6
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(b[1]) or tonumber(ARGV[2])
local ts = tonumber(b[2]) or now
tokens = math.min(tonumber(ARGV[2]), tokens + (now - ts) * tonumber(ARGV[1]))
local ok = tokens >= tonumber(ARGV[3])
if ok then tokens = tokens - tonumber(ARGV[3]) end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('EXPIRE', KEYS[1], 3600)
return ok and 1 or 0

Что хотят услышать

  • Почему нужен общий счётчик и где разместить лимитер.
  • Атомарность проверки и списания (Lua), время из одного источника.
  • Выбор алгоритма и его стоимость в Redis.
  • Снижение задержки локальными квотами, поведение при отказе Redis.
  • Ответ клиенту и наблюдаемость.

Частые ошибки

  • Читать счётчик и записывать отдельными командами — гонка между шлюзами.
  • Не продумать, что делать при недоступности Redis.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем