Rate Limiting в Go (Golang): библиотеки, паттерны и примеры реализации

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Rate Limiting в Go (Golang): библиотеки, паттерны и примеры реализации

Rate limiting (ограничение скорости запросов) — один из ключевых механизмов защиты и контроля нагрузки в современных веб-сервисах и микросервисной архитектуре. В экосистеме Go (Golang) существует несколько зрелых инструментов для реализации rate limiting, от встроенного пакета time до специализированных библиотек, таких как golang.org/x/time/rate. В этой статье мы подробно разберём, как работает rate limiting в Go, какие библиотеки использовать и приведём практические примеры кода.

Что такое Rate Limiting и зачем он нужен?

Rate limiting — это техника контроля количества запросов, которые клиент может выполнить к серверу за определённый промежуток времени. Основные цели:

  • Защита от DDoS-атак — предотвращение перегрузки сервера.
  • Честное распределение ресурсов — ограничение "прожорливых" клиентов.
  • Соблюдение SLA — гарантия доступности для всех пользователей.
  • Экономия затрат — снижение нагрузки на базы данных и внешние API.

В Go rate limiting реализуется с помощью алгоритмов Token Bucket (ведро токенов) и Leaky Bucket (дырявое ведро). Наиболее популярен Token Bucket.

Библиотека golang.org/x/time/rate

Основная и рекомендуемая библиотека для rate limiting в Go — это golang.org/x/time/rate. Она реализует алгоритм Token Bucket и входит в расширенный набор пакетов Go (sub-repositories).

Установка:

go get golang.org/x/time/rate

Основные компоненты

  • rate.Limiter — основной объект ограничителя.
  • rate.NewLimiter(r rate.Limit, b int) — создаёт лимитер с частотой r запросов в секунду и размером ведра b.
  • Allow() — проверяет, можно ли выполнить запрос сейчас (неблокирующая).
  • Wait(ctx) — блокирует выполнение, пока не появится доступный токен (учитывает контекст).
  • Reserve() — возвращает Reservation, позволяющий отложить выполнение.

Пример 1: Базовый rate limiter

package main

import (
    "fmt"
    "golang.org/x/time/rate"
    "time"
)

func main() {
    // 2 запроса в секунду, ведро на 5 токенов
    limiter := rate.NewLimiter(2, 5)

    for i := 0; i < 10; i++ {
        if limiter.Allow() {
            fmt.Printf("Запрос %d выполнен\n", i)
        } else {
            fmt.Printf("Запрос %d отклонён (лимит)\n", i)
        }
        time.Sleep(200 * time.Millisecond)
    }
}

Пример 2: Rate limiter с ожиданием (Wait)

package main

import (
    "context"
    "fmt"
    "golang.org/x/time/rate"
    "time"
)

func main() {
    limiter := rate.NewLimiter(1, 3) // 1 запрос в секунду, ведро на 3
    ctx := context.Background()

    for i := 0; i < 6; i++ {
        err := limiter.Wait(ctx)
        if err != nil {
            fmt.Println("Ошибка ожидания:", err)
            return
        }
        fmt.Printf("Запрос %d выполнен в %s\n", i, time.Now().Format("15:04:05.000"))
    }
}

Реализация HTTP middleware с rate limiting

На практике rate limiting чаще всего применяется на уровне HTTP-сервера. Рассмотрим пример middleware для Gin или стандартного net/http.

package main

import (
    "net/http"
    "golang.org/x/time/rate"
)

// RateLimiterMiddleware возвращает middleware с rate limit
func RateLimiterMiddleware(r rate.Limit, b int) func(http.Handler) http.Handler {
    limiter := rate.NewLimiter(r, b)
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    // Оборачиваем в middleware: 10 запросов в секунду, ведро на 20
    handler := RateLimiterMiddleware(10, 20)(m                    

Рекомендаций