Рейт-лимитер — штука, которую обычно добавляют в панике после первого всплеска трафика. Расскажу, как это выглядит в небольшой команде без выделенного продвинутого API-gateway.
Алгоритм. Leaky bucket сглаживает поток до ровной скорости — звучит красиво, но подход режет ожидаемые всплески (страница открывает десять запросов параллельно — и половина возвращает 429). Token bucket наполняется равномерно, но позволяет потратить накопленные токены пачкой, вплоть до размера ведра. Для HTTP API в 99% случаев нужен именно token bucket. В Go есть
golang.org/x/time/rate — с него и стоит начинать.
func rateLimit(next http.Handler) http.Handler {
limiters := sync.Map{}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-API-Key")
l, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Every(time.Second), 20))
if !l.(*rate.Limiter).Allow() {
http.Error(w, "too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
Скоуп. Обычно одного лимита мало. Нужны три оси сразу: по IP (базовая защита), по пользователю, по API-ключу (тарифы).
Где ставить. Middleware в приложении видит авторизацию — значит, лимиты по юзеру и ключу живут там. Как только инстансов больше одного — переезжайте на Redis. Про лимиты на уровне Nginx будет отдельный пост.
А вы где режете трафик — на гейте, в приложении, или и там и там?