Как реализовать ограничение частоты запросов (rate limiting) к эндпоинту API?

Ответ

Для ограничения частоты запросов к API (rate limiting) в Go чаще всего используют пакет golang.org/x/time/rate. Он реализует алгоритм "token bucket" (метод токенов в ведре).

Основная идея — есть "ведро" с определенной емкостью, которое пополняется токенами с фиксированной скоростью. Каждый запрос "забирает" один токен. Если токенов нет, запрос блокируется или отклоняется.

Пример с golang.org/x/time/rate:

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

// Создаем limiter: 1 токен в секунду, с "ведром" на 5 токенов.
// Это позволяет обрабатывать короткие всплески до 5 запросов.
var limiter = rate.NewLimiter(1, 5)

// Middleware для проверки ограничения
func limitMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Allow() проверяет, можно ли взять токен. Если нет, возвращает false.
        if !limiter.Allow() {
            http.Error(w, "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
    http.ListenAndServe(":8080", limitMiddleware(mux))
}

Распределенные системы

Для систем, работающих на нескольких инстансах, локальный limiter не подойдет, так как каждый инстанс будет иметь свой собственный счетчик. В таких случаях состояние лимитера (количество токенов) выносят во внешнее хранилище, например, Redis, используя атомарные операции для управления счетчиками.