Rate Limiting в Go: полное руководство по ограничению скорости запросов
Rate limiting (ограничение скорости запросов) — это важнейший механизм защиты и управления нагрузкой в современных веб-приложениях и микросервисах. В языке Go (Golang) существует множество подходов и библиотек для реализации rate limiting, от простых до продвинутых. В этой статье мы разберем основные концепции, популярные библиотеки и покажем примеры кода.
Что такое Rate Limiting и зачем он нужен?
Rate limiting — это техника контроля количества запросов, которые клиент может отправить за определенный промежуток времени. Основные цели:
- Защита от DDoS-атак — предотвращение перегрузки сервера
- Справедливое распределение ресурсов — равный доступ для всех пользователей
- Экономия ресурсов — снижение затрат на инфраструктуру
- Предотвращение злоупотреблений — защита от скрапинга и брутфорса
Основные алгоритмы Rate Limiting
Существует несколько классических алгоритмов, каждый со своими преимуществами:
- Token Bucket (Токен-ведро) — самый популярный алгоритм, где "токены" накапливаются с определенной скоростью
- Leaky Bucket (Дырявое ведро) — запросы обрабатываются с фиксированной скоростью, избыток отбрасывается
- Fixed Window (Фиксированное окно) — простой счетчик запросов за фиксированный интервал времени
- Sliding Window (Скользящее окно) — более точный, чем Fixed Window, учитывает временные границы
Популярные библиотеки для Rate Limiting в Go
1. Стандартная библиотека: golang.org/x/time/rate
Это официальное расширение от команды Go, реализующее алгоритм Token Bucket. Она легковесна, проста в использовании и идеально подходит для большинства сценариев.
package main
import (
"fmt"
"net/http"
"time"
"golang.org/x/time/rate"
)
func main() {
// Создаем лимитер: 2 запроса в секунду, с burst 5
limiter := rate.NewLimiter(2, 5)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Слишком много запросов", http.StatusTooManyRequests)
return
}
fmt.Fprintf(w, "OK: %s", time.Now().Format(time.RFC3339))
})
http.ListenAndServe(":8080", nil)
}
2. github.com/ulule/limiter
Более гибкая библиотека с поддержкой различных хранилищ (Redis, Memcached, in-memory) и алгоритмов. Подходит для распределенных систем.
import (
"github.com/gin-gonic/gin"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/memory"
)
func main() {
// Настройка: 10 запросов в секунду
rate := limiter.Rate{
Period: 1 * time.Second,
Limit: 10,
}
store := memory.NewStore()
instance := limiter.New(store, rate)
r := gin.Default()
r.Use(func(c *gin.Context) {
key := c.ClientIP()
context, err := instance.Get(c.Request.Context(), key)
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "Internal error"})
return
}
if context.Reached {
c.AbortWithStatusJSON(429, gin.H{"error": "Too many requests"})
return
}
c.Next()
})
r.Run(":8080")
}
3. github.com/juju/ratelimit
Простая и надежная библиотека от Canonical, основанная на алгоритме Token Bucket. Отличается минималистичным API.
import (
"github.com/juju/ratelimit"
"time"
)
func main() {
// Создаем bucket: 100 токенов, пополнение 10 токенов в секунду
bucket := ratelimit.NewBucketWithRate(10, 100)
for i := 0; i < 200; i++ {
if bucket.TakeAvailable(1) > 0 {
fmt.Println("Запрос выполнен:", i)
} else {
fmt.Println("Превышен лимит:", i)
time.Sleep(100 * time.Millisecond)
}
}
}
Продвинутые сценарии использования
Rate Limiting с Redis для распределенных систем
Когда у вас несколько инстансов приложения, необходимо централизованное хранилище. Redis идеально подходит для этой задачи.
import (
"github.com/go-redis/redis/v8"
"github.com/ulule/limiter/v3/drivers/store/redis"
)