← Назад к вопросам

Как ограничить количество использований ручки API?

2.2 Middle🔥 171 комментариев
#Производительность и оптимизация#Сетевые протоколы и API

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ограничение количества использований ручки API в Go

В Go ограничение количества вызовов API (или "ручки") является важной частью разработки для защиты от перегрузки сервера, обеспечения справедливого использования ресурсов и предотвращения атак типа DoS. Я реализовал подобные системы в нескольких проектах и могу выделить несколько ключевых подходов.

Основные стратегии ограничения

  1. Rate Limiting на основе IP или пользователя Самый распространённый метод — отслеживание количества запросов от конкретного клиента за определённый промежуток времени.

  2. Глобальное ограничение на весь сервер Ограничение общего числа запросов к определённому endpoint, независимо от источника.

  3. Токены (Quotas) для API ключей Если API использует ключи, можно назначать каждому ключу определённое количество запросов в день/месяц.

Практическая реализация в Go

Для реализации rate limiting в Go я обычно использую один из следующих подходов:

1. Использование middleware с хранением состояния в памяти

package main

import (
    "net/http"
    "sync"
    "time"
)

type RateLimiter struct {
    requests map[string][]time.Time
    limit    int
    window   time.Duration
    mu       sync.RWMutex
}

func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
    return &RateLimiter{
        requests: make(map[string][]time.Time),
        limit:    limit,
        window:   window,
    }
}

func (rl *RateLimiter) Allow(key string) bool {
    rl.mu.Lock()
    defer rl.mu.Unlock()
    
    now := time.Now()
    windowStart := now.Add(-rl.window)
    
    // Очищаем старые записи
    validRequests := []time.Time{}
    for _, t := range rl.requests[key] {
        if t.After(windowStart) {
            validRequests = append(validRequests, t)
        } 
    }
    
    if len(validRequests) >= rl.limit {
        return false
    }
    
    validRequests = append(validRequests, now)
    rl.requests[key] = validRequests
    return true
}

func RateLimitMiddleware(rl *RateLimiter, keyFunc func(*http.Request) string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            key := keyFunc(r)
            if !rl.Allow(key) {
                w.WriteHeader(http.StatusTooManyRequests)
                w.Write([]byte("Rate limit exceeded"))
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

2. Использование готовых библиотек В производственных условиях я часто использую библиотеки, которые уже решают многие сложности:

  • github.com/go-redis/redis_rate — для распределенного rate limiting через Redis
  • github.com/ulule/limiter — универсальный лимитер с поддержкой различных хранилищ
  • x/time/rate — стандартная библиотека Go, предоставляющая базовый механизм

Пример с x/time/rate:

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

func RateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                w.WriteHeader(http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

// Использование
func main() {
    // 10 запросов в секунду, с burst до 20
    limiter := rate.NewLimiter(rate.Limit(10), 20)
    
    handler := RateLimitMiddleware(limiter)(yourHandler)
    http.ListenAndServe(":8080", handler)
}

Ключевые аспекты для производственного использования

  • Выбор ключа для ограничения: IP адрес, API ключ, пользовательский ID или комбинация параметров.
  • Хранение состояния:
    • In-memory — быстро, но не распределённо и не сохраняется между рестартами
    • Redis/Redis Cluster — распределённое решение для микросервисной архитектуры
    • Database — для персистентного долгосрочного учёта (например, месячные лимиты)
  • Настройка лимитов: обычно конфигурируются через environment variables или отдельный config файл.
  • Возвращаемые ответы: При превышении лимита следует возвращать статус 429 Too Many Requests с возможными деталями (когда лимит восстановится).

Пример конфигурации через middleware

В реальном проекте я обычно создаю гибкую систему, которая позволяет настраивать разные лимиты для разных endpoint:

type RouteLimitConfig struct {
    Path    string
    Limit   int
    Window  time.Duration
    KeyFunc func(*http.Request) string
}

func MultiRouteRateLimiter(configs []RouteLimitConfig) map[string]*RateLimiter {
    limiters := make(map[string]*RateLimiter)
    for _, config := range configs {
        limiters[config.Path] = NewRateLimiter(config.Limit, config.Window)
    }
    return limiters
}

Заключение

Ограничение использования API ручек в Go — это многогранная задача, требующая учёта архитектуры приложения, требований к распределению и бизнес-логики. Начинать можно с простого in-memory rate limiting через middleware, но для производственных систем я рекомендую использовать распределённые решения на основе Redis с готовыми библиотеками, которые обеспечивают надёжность, масштабируемость и rich функциональность.

Как ограничить количество использований ручки API? | PrepBro