摘要:限流(Rate Limiting)是一种通过控制请求处理速率来保护系统的技术,它能有效防止服务器因突发流量或恶意攻击而过载,确保服务的稳定性和可用性。令牌桶算法是一种常见的限流算法,其基本原理是系统以固定的速率向一个桶中添加"令牌",请求处理需要从桶中获取令牌
限流(Rate Limiting) 是一种通过控制请求处理速率来保护系统的技术,它能有效防止服务器因突发流量或恶意攻击而过载,确保服务的稳定性和可用性。令牌桶算法是一种常见的限流算法,其基本原理是系统以固定的速率向一个桶中添加"令牌",请求处理需要从桶中获取令牌,若桶中没有足够的令牌,则拒绝请求。这种算法 允许一定程度的突发流量 (取决于桶的容量),同时能 将长期请求速率稳定在预设值 。
你可以手动实现一个令牌桶结构,这种方式灵活度 #技术分享高,便于深度定制。
package mainimport ( "net/http" "sync" "time" "github.com/gin-gonic/gin" )type TokenBucket struct { rate float64 capacity float64 tokens float64 lastRefill time.Time mutex sync.Mutex }func NewTokenBucket(rate float64, capacity float64) *TokenBucket { return &TokenBucket{ rate: rate, capacity: capacity, tokens: capacity, lastRefill: time.Now, } }func (tb *TokenBucket) Allow bool { tb.mutex.Lock defer tb.mutex.Unlock now := time.Now elapsed := now.Sub(tb.lastRefill).Seconds tb.lastRefill = now tb.tokens += elapsed * tb.rate if tb.tokens > tb.capacity { tb.tokens = tb.capacity } if tb.tokens >= 1 { tb.tokens -= 1 return true } return false }type RateLimiter struct { clients map[string]*TokenBucket mutex sync.Mutex rate float64 capacity float64 }func NewRateLimiter(rate float64, capacity float64) *RateLimiter { return &RateLimiter{ clients: make(map[string]*TokenBucket), rate: rate, capacity: capacity, } }func (rl *RateLimiter) GetTokenBucket(clientID string) *TokenBucket { rl.mutex.Lock defer rl.mutex.Unlock tb, exists := rl.clients[clientID] if !exists { tb = NewTokenBucket(rl.rate, rl.capacity) rl.clients[clientID] = tb } return tb }func RateLimitmiddleware(rl *RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { clientIP := c.ClientIP tb := rl.GetTokenBucket(clientIP) if tb.Allow { c.Next } else { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": "Too Many Requests", }) return } } }func main { router := gin.Default rateLimiter := NewRateLimiter(5, 10) router.Use(RateLimitMiddleware(rateLimiter)) router.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "Hello, World!", }) }) router.Run(":8080") }Go 语言的标准库 golang.org/x/time/rate 提供了基于令牌桶算法的限流器实现,这是官方维护的方案,值得考虑。
package mainimport ( "net/http" "sync" "time" "github.com/gin-gonic/gin" "golang.org/x/time/rate" )type Client struct { limiter *rate.Limiter lastSeen time.Time }type RateLimiter struct { clients map[string]*Client mutex sync.Mutex r rate.Limit b int }func NewRateLimiter(r rate.Limit, b int) *RateLimiter { rl := &RateLimiter{ clients: make(map[string]*Client), r: r, b: b, } go rl.cleanupClients return rl }func (rl *RateLimiter) GetLimiter(clientID string) *rate.Limiter { rl.mutex.Lock defer rl.mutex.Unlock client, exists := rl.clients[clientID] if !exists { limiter := rate.NewLimiter(rl.r, rl.b) rl.clients[clientID] = &Client{ limiter: limiter, lastSeen: time.Now, } return limiter } client.lastSeen = time.Now return client.limiter }func (rl *RateLimiter) cleanupClients { for { time.Sleep(time.Minute) rl.mutex.Lock for clientID, client := range rl.clients { if time.Since(client.lastSeen) > 3*time.Minute { delete(rl.clients, clientID) } } rl.mutex.Unlock } }func RateLimitMiddleware(rl *RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { clientIP := c.ClientIP limiter := rl.GetLimiter(clientIP) if limiter.Allow { c.Next } else { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": "Too Many Requests", }) return } } }func main { router := gin.Default rateLimiter := NewRateLimiter(10, 20) router.Use(RateLimitMiddleware(rateLimiter)) router.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) router.Run(":8080") }对于需要 分布式限流 的场景,github.com/ulule/limiter/v3 库是一个不错的选择,它支持多种存储后端(如内存、Redis 等)。
package mainimport ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/ulule/limiter/v3" "github.com/ulule/limiter/v3/drivers/store/memory" mgin "github.com/ulule/limiter/v3/drivers/middleware/gin" )func main { router := gin.Default rate := limiter.Rate{ Period: 1 * time.Minute, Limit: 100, } store := memory.NewStore limiterInstance := limiter.New(store, rate) middleware := mgin.NewMiddleware(limiterInstance) router.Use(middleware) router.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) router.Run(":8080") }若要使用 Redis 作为存储后端以实现分布式限流,可以这样做:
import ( "github.com/go-redis/redis/v8" "github.com/ulule/limiter/v3" "github.com/ulule/limiter/v3/drivers/store/redis")func RateLimitMiddleware gin.HandlerFunc { rate := limiter.Rate{ Period: 1 * time.Minute, Limit: 100, } client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) store, err := redisstore.NewWithClient(client) if err != nil { panic(err) } limiterInstance := limiter.New(store, rate) middleware := mgin.NewMiddleware(limiterInstance) return middleware }下表对比了几种常见的限流实现方式,帮助你根据实际场景做出选择:
| 特性 | 手动实现令牌桶 | golang.org/x/time/rate | ulule/limiter (内存) | ulule/limiter (Redis) | | ---
| 实现复杂度 | 高 | 中 | 低 | 低 | | 分布式支持 | 否 | 否 | 否 | 是 | | 性能 | 取决于实现 | 高 | 高 | 中(网络依赖) | | 功能灵活性 | 极高 | 中 | 中 | 中 | | 适用场景 | 高度定制需求 | 单机应用 | 单机应用 | 集群环境 |
不同的路由或用户组可能需要不同的限流策略:
func main { router := gin.Default globalLimiter := NewRateLimiter(100, 200) router.Use(RateLimitMiddleware(globalLimiter)) v1 := router.Group("/api/v1") v1Limiter := NewRateLimiter(50, 100) v1.Use(RateLimitMiddleware(v1Limiter)) { v1.GET("/users", getUsersHandler) v1.GET("/products", getProductsHandler) } auth := router.Group("/auth") authLimiter := NewRateLimiter(200, 400) auth.Use(RateLimitMiddleware(authLimiter)) { auth.POST("/login", loginHandler) auth.POST("/register", registerHandler) } router.Run(":8080")}令牌桶算法的一个优势是能 处理一定程度的突发流量 。通过合理设置桶容量 ( capacity ),你可以控制允许的突发流量大小。例如,设置 rate=10 (每秒10个令牌)和 capacity=30 ,意味着系统平时每秒处理10个请求,但最多可应对30个请求的突发流量。
为了更好了解限流效果,可以添加监控和日志记录:
func RateLimitMiddlewareWithLogging(rl *RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { clientIP := c.ClientIP tb := rl.GetTokenBucket(clientIP) if tb.Allow { log.Printf("Request allowed from %s, tokens remaining: %f", clientIP, tb.tokens) c.Next } else { log.Printf("Request limited from %s", clientIP) c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": "Too Many Requests", "retry_after": 60, }) return } }} 测试限流效果正常响应应包含 HTTP/1.1 200 OK ,而被限流的请求会返回 HTTP/1.1 429 Too Many Requests 。
内存泄漏风险 :手动实现的限流器可能因存储过多客户端信息而导致内存泄漏。解决方案是定期清理不活跃的客户端。分布式环境一致性 :在集群部署中,需要使用 Redis 等外部存储来同步限流状态。网关层限流 :对于特别高流量的场景,考虑在 API 网关层(如 Nginx、Traefik)实施限流,减轻应用层压力。用户体验优化 :对于被限流的请求,可以返回 Retry-After 头部,告知客户端何时可以重试。对于 单机应用 , golang.org/x/time/rate 包是简单可靠的选择。需要 分布式支持 时, ulule/limiter 与 Redis 搭配是常见方案。有 特殊需求 时,可考虑手动实现令牌桶逻辑。限流策略应根据实际业务场景调整,并配合监控日志,才能在保护服务的同时提供良好的用户体验。
来源:墨码行者