Требования: лимиты по 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; лимиты по тарифу из конфигурации, которую можно менять без выкатки; отдельные лимиты на тяжёлые операции (стоимость в жетонах).
Наблюдаемость: метрики отказов по клиентам и правилам — чтобы отличить атаку от ошибки конфигурации, которая режет честных клиентов.