限流中间件:golang.org/x/time/rate 令牌桶接入 Gin
限流保护后端不被突发流量打爆。最常用的扩展包是 golang.org/x/time/rate,它实现令牌桶:按固定速率向桶里放令牌,每次请求取一枚,桶满则拒绝。安装:go get golang.org/x/time/rate。
1. 令牌桶基本用法
import "golang.org/x/time/rate"
// 每秒放 5 枚令牌,桶容量 10(允许最多 10 个瞬时突发)
limiter := rate.NewLimiter(rate.Limit(5), 10)
limiter.Allow() // 立即取一枚,不够返回 false(不阻塞)
limiter.Wait(ctx) // 阻塞等待直到有令牌,ctx 取消则返回错误
Allow 适合做成「快速失败」返回 429;Wait 适合平滑排队。
2. 按客户端 IP 限流的中间件
每 IP 一个 limiter,需要一个带锁的 map 做查找:
type ipLimiter struct {
mu sync.Mutex
limiters map[string]*rate.Limiter
}
func (l *ipLimiter) get(ip string) *rate.Limiter {
l.mu.Lock()
defer l.mu.Unlock()
lim, ok := l.limiters[ip]
if !ok {
lim = rate.NewLimiter(rate.Limit(10), 20) // 每 IP 每秒 10 次
l.limiters[ip] = lim
}
return lim
}
func RateLimit() gin.HandlerFunc {
lim := &ipLimiter{limiters: make(map[string]*rate.Limiter)}
return func(c *gin.Context) {
if !lim.get(c.ClientIP()).Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests,
gin.H{"error": "请求过于频繁,请稍后再试"})
return
}
c.Next()
}
}
3. 挂载与验证
r := gin.Default()
r.Use(RateLimit()) // 全局限流
r.GET("/api/search", SearchHandler)
用 curl 连续请求 /api/search,超过桶容量后应看到 HTTP/1.1 429。
4. 按用户或接口维度限流
思路不变,只是 key 从 IP 换成用户 ID 或「接口名+用户」:
- 登录接口:按 IP 限,防撞库;
- 下单接口:按
userID限,防刷单; - 注意 map 会无限增长:定期清理或改用 LRU 缓存。
注意点
- map 非并发安全,任何读写都要加锁。
- 限流状态在内存里,多实例部署各自独立;要全局生效需分布式限流(Redis 滑动窗口/令牌桶)。
- 429 响应建议带
Retry-After头,提示客户端何时重试。 - 别把 IP 当唯一标识:NAT 后多用户共享 IP 会被误伤,可叠加账号维度。
小结
单机限流 = rate.NewLimiter(速率, 容量) + 每 key 一个实例 + 429 快速失败。先想清限的维度(IP/用户/接口),再决定用 Allow 还是 Wait,压测调参时注意观察误杀率。