Введение
Защита высоконагруженных публичных API требует двух ключевых гарантий: предотвращения перегрузки внутренних сервисов при всплесках трафика и обеспечения однократного выполнения дублирующихся клиентских запросов. Для этого применяются масштабируемые подсистемы ограничения частоты запросов (rate limiting) и идемпотентности на базе Redis. Ниже рассматриваются архитектурные решения и практические шаблоны для их реализации в распределённой среде.
Распределённое ограничение частоты запросов: Sliding Window Log и Token Bucket
В мультитенантных платформах простые фиксированные окна (fixed window) подвержены уязвимости на границе окна: клиент может отправить вдвое больше разрешённых запросов в момент сброса счётчика. Для предсказуемого и плавного ограничения используют атомарные операции Redis.
Шаблон Sliding Window Log в Redis
Для отслеживания запросов в скользящем временном окне применяют отсортированные множества (ZSET). Каждый запрос фиксируется с временной меткой. Атомарный Lua-скрипт:
- удаляет устаревшие метки за пределами окна (ZREMRANGEBYSCORE);
- подсчитывает количество актуальных запросов (ZCARD);
- при наличии лимита добавляет новую метку с микросекундной уникальностью (ZADD) и обновляет TTL (EXPIRE);
- возвращает разрешение или отказ с остатком лимита.
Такой подход обеспечивает сложность O(log N + M) и корректную работу при высокой конкурентности, исключая скачки на границах окна.
Альтернатива: Token Bucket
Token Bucket реализует «запас токенов», пополняемый с фиксированной скоростью. Каждый запрос расходует токен. В Redis это реализуется через атомарные скрипты с использованием строковых ключей или списков, где хранится текущий объём и время последнего пополнения. Преимущество — предсказуемая средняя скорость и возможность краткосрочных всплесков до размера «ведра».
Идемпотентность API: исключение повторных побочных эффектов
Сетевые таймауты, повторные попытки клиентов и циклы доставки вебхуков приводят к дублированию полезной нагрузки. Слой идемпотентности перехватывает запросы с заголовком Idempotency-Key и гарантирует однократное выполнение логики.
Архитектура идемпотентного слоя
- Клиент генерирует уникальный ключ идемпотентности и передаёт его в заголовке.
- На входе API проверяет Redis на наличие результата по этому ключу (GET).
- Если результат есть — возвращается сохранённый ответ без повторной обработки.
- Если нет — запрос проходит в бизнес-логику; по завершении результат (статус, тело) сохраняется в Redis с ключом идемпотентности и TTL (SET с EXPIRE), часто в транзакции или через Lua для атомарности.
Для предотвращения «потерянных» обновлений при одновременных одинаковых запросах применяют:
- SETNX для создания записи только при её отсутствии;
- или короткоживущие блокировки (RedLock/базовый Redis lock) с ключом вида lock:{idempotency_key}, чтобы первый воркер обрабатывал запрос, а остальные ожидали или получали временный ответ «принято в обработке».
Управление жизненным циклом
TTL подбирают исходя из бизнес-требований: от нескольких минут до суток. Для тяжёлых операций полезно хранить промежуточный статус «processing», чтобы повторные запросы не запускали дублирующие задачи. Очистка старых ключей может быть дополнена периодическими задачами, но TTL — основной механизм.
Практические рекомендации
- Используйте Lua-скрипты для атомарности и предотвращения гонок.
- Для Sliding Window храните только метки внутри окна; регулярно обрезайте ZSET для контроля объёма.
- Для Token Bucket следите за точностью времени пополнения и атомарностью обновления «ведра».
- В слое идемпотентности фиксируйте финальный ответ, а не только факт выполнения, чтобы все повторные запросы получали идентичный результат.
- Настраивайте разумные TTL и лимиты по тенантам; изолируйте ключи по префиксам (например, tenant:{id}:rate:resource).
- Мониторьте латентность Lua-скриптов, насыщенность Redis и частоту отказов по лимитам; предусмотрите «мягкие» деградации (best-effort) при перегрузке.
Безопасность и устойчивость
- Ограничивайте выполнение Lua-скриптов по времени.
- Избегайте длинных блокировок; используйте короткие таймауты и автоматический релиз ключей.
- Шардирование и репликация Redis должны учитывать консистентность для идемпотентности: при критичных требованиях — режимы с повышенной согласованностью или дополнительные подтверждения.
Эти шаблоны позволяют строить масштабируемые, предсказуемые и надёжные подсистемы ограничения частоты запросов и идемпотентности, устойчивые к всплескам трафика и сетевым аномалиям.