Ответ
Rate Limiter (ограничитель скорости запросов) можно и нужно реализовывать на разных уровнях архитектуры, в зависимости от целей. Вот основные из них, от внешнего к внутреннему:
-
Уровень шлюза / балансировщика (API Gateway / Load Balancer)
- Где: Nginx, Kong, Traefik, AWS API Gateway.
- Зачем: Это первая линия защиты. Ограничивает трафик еще до того, как он попадет в ваши сервисы. Защищает всю инфраструктуру от DDoS-атак и всплесков трафика. Обычно настраивается по IP-адресу или API-ключу.
-
Уровень приложения (HTTP Middleware)
- Где: В коде самого приложения, как промежуточный слой для обработки HTTP-запросов. Это самый распространенный способ для бэкенд-сервисов.
- Зачем: Позволяет реализовать гибкую логику: ограничение для конкретного пользователя, эндпоинта или на основе бизнес-логики. Часто используется алгоритм "Token Bucket" (Ведро с токенами).
// Пример с использованием стандартной библиотеки golang.org/x/time/rate func RateLimit(next http.Handler) http.Handler { // 10 запросов в секунду с возможностью "всплеска" до 20 запросов limiter := rate.NewLimiter(rate.Limit(10), 20) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !limiter.Allow() { http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) return } next.ServeHTTP(w, r) }) }
-
Распределенный Rate Limiter (Distributed Rate Limiter)
- Где: Использует внешнее хранилище, такое как Redis или Memcached.
- Зачем: Критически важен для горизонтально масштабируемых приложений. Если у вас несколько экземпляров сервиса, локальный ограничитель (как в примере выше) будет работать некорректно. Централизованное хранилище (например, Redis и его атомарные команды
INCR,EXPIRE) обеспечивает единый счетчик для всех экземпляров.
-
Уровень бизнес-логики
- Где: Внутри конкретной функции или метода сервиса.
- Зачем: Для ограничения специфических, ресурсоемких операций, не связанных напрямую с HTTP-запросами. Например: "не более 5 отправок SMS в час для одного пользователя" или "не более 1 генерации отчета в минуту".
Ответ 18+ 🔞
А, слушай, про Rate Limiter! Ну это ж классика, блядь. Как в том анекдоте про очередь в сортир — если все ринутся разом, будет пиздец, а не дела. Так и тут.
Вот смотри, эту штуку можно, а часто и нужно, впиливать на разных этажах твоей архитектуры, от крыльца до самого погреба. Главное — понимать, зачем на каждом уровне. Поехали по слоям, от внешнего к внутреннему.
1. Уровень шлюза или балансировщика (API Gateway / Load Balancer)
- Где это: Nginx, Kong, Traefik, AWS API Gateway — вся эта братия.
- Зачем, блядь: Это твой бронезаслон, первая линия обороны! Всякий левый трафик отсекается ещё до того, как он доберётся до святая святых — твоих сервисов. Идеально, чтобы вся инфраструктура не легла, как подкошенная, от какого-нибудь внезапного DDoS или просто от распиздяев, которые дергают API как сумасшедшие. Тут обычно бьют по IP или API-ключу. Просто, железно, эффективно.
2. Уровень самого приложения (HTTP Middleware)
- Где это: Прям в теле твоего бэкенд-сервиса, как промежуточный слой-фильтр для HTTP-запросов. Самый, наверное, народный способ.
- Зачем: А вот тут уже можно похитрее, блядь! Не просто "с этого IP много", а "этому юзеру — столько, этому эндпоинту — эдак, а на эту операцию — вот такой лимит". Гибкость, ёпта! Часто внутри используется алгоритм "Ведро с токенами" (Token Bucket). Смотри, как просто в Go:
// Пример с использованием стандартной библиотеки golang.org/x/time/rate
func RateLimit(next http.Handler) http.Handler {
// 10 запросов в секунду с возможностью "всплеска" до 20 запросов
limiter := rate.NewLimiter(rate.Limit(10), 20)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
Но есть нюанс, блядь! Если у тебя сервис размножился на несколько инстансов, этот локальный лимитер превратится в манда-с-ушами. У каждого своё ведро, и общий лимит проебётся.
3. Распределённый Rate Limiter (Distributed Rate Limiter)
- Где это: Тут уже подключаем внешнее хранилище — Redis, Memcached. Без них никуда.
- Зачем, ёпта: Это как раз ответ на проблему выше! Для горизонтально масштабируемого приложения — святая вода. Все инстансы тычутся в один центральный счётчик в Redis (через его атомарные команды
INCRиEXPIRE), и поэтому знают общую картину. Никаких "ой, а я думал, он ещё не превысил". Все в курсе, блядь.
4. Уровень бизнес-логики
- Где это: Прям внутри какой-нибудь конкретной функции, которая делает что-то дорогое или опасное.
- Зачем: Это уже не про HTTP, а про внутренние тараканы. Например: "не больше пяти смсок в час юзеру, а то спамить начнёт" или "генерацию отчёта — не чаще раза в минуту, а то сервак взвоет". Тут уже твоя фантазия и требования бизнеса рулят.
Короче, выбор уровня — это не "или-или", а часто "и-и". На шлюзе отбиваешь грубые атаки, в приложении — тонкую логику для легитов, а в бизнес-процессах следишь, чтобы свои же не угробили систему жадностью. Всё просто, как три копейки!