Как ограничить количество запросов клиента к сервису
APIбезопасностьзапросыограничениепроизводительность
Что проверяют
Знаете ли вы алгоритмы ограничения частоты и понимаете ли разницу между локальным и распределённым счётчиком.
Как отвечать
Сначала алгоритмы:
- токенное ведро: токены пополняются с постоянной скоростью, запрос забирает токен. Допускает короткие всплески в пределах объёма ведра, и это самый частый выбор. В Go есть готовая реализация
golang.org/x/time/rate; - дырявое ведро: выравнивает поток, всплески не пропускает;
- фиксированное окно: просто считать, но на границе окон пропускает двойную нагрузку;
- скользящее окно: точнее, дороже по памяти.
Реализация в Go для одного экземпляра выглядит как ограничитель на клиента, хранимый в карте с вытеснением по времени, и проверка в промежуточном обработчике:
if !limiterFor(clientKey).Allow() {
w.Header().Set("Retry-After", "1")
http.Error(w, "too many requests", http.StatusTooManyRequests)
return
}Что обязательно назвать:
- ответ 429 с заголовком
Retry-After, а лучше и с заголовками об оставшемся лимите, чтобы клиент мог подстроиться; - ключ ограничения: идентификатор пользователя или ключ API надёжнее, чем адрес, потому что за одним адресом сидит целый офис, а адрес легко сменить;
- несколько экземпляров сервиса: локальные счётчики дают лимит, умноженный на число экземпляров. Для общего лимита нужен внешний счётчик, обычно в Redis со скриптом, выполняющим проверку и инкремент атомарно;
- разные лимиты для разных ручек: тяжёлый поиск и лёгкое чтение справочника не должны иметь один порог;
- уровень выше приложения: часть ограничений дешевле поставить на обратном прокси или шлюзе.
Где спотыкаются
Ограничивают по адресу и режут целые организации за одним общим выходом в сеть.
Считают запросы в памяти при нескольких экземплярах и получают лимит, отличающийся от заявленного.
Возвращают 403 вместо 429, и клиент не понимает, что надо просто подождать.