← 返回博客
go2026-09-12 22:01:142 分钟 · 469 0

Gin 接口限流:令牌桶与分布式限流

用 golang.org/x/time/rate 做单机令牌桶限流,封装成 gin 中间件,再上 redis + lua 实现多实例共享的分布式限流。

#gin#Redis#限流

服务被突发流量或爬虫打满,正常用户也进不来。限流在入口挡掉超额请求,保护自身和下游。这一篇做两层:单机令牌桶兜日常,分布式令牌桶兜多实例部署。

1. 令牌桶原理

桶里按固定速率放令牌,请求来取一枚,取不到就拒。它允许短时突发(桶里有存量),比固定窗口平滑。Go 标准扩展库自带实现。

2. 单机中间件

import "golang.org/x/time/rate"

func RateLimit(rps float64, burst int) gin.HandlerFunc {
    limiter := rate.NewLimiter(rate.Limit(rps), burst)
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.JSON(429, gin.H{"error": "请求过于频繁"})
            c.Abort()
            return
        }
        c.Next()
    }
}

rps 是每秒放令牌数,burst 是桶容量(允许的突发量)。Allow() 无阻塞,超了立即返 429。

3. 按用户限流更公平

全局限流会被一个用户占满。用 limiter map 按 uid 隔离,带懒清理:

var users = &sync.Map{}

func RateLimitPerUser(rps float64, burst int) gin.HandlerFunc {
    return func(c *gin.Context) {
        uid, _ := c.Get("uid")
        key := fmt.Sprint(uid)
        v, _ := users.LoadOrStore(key, rate.NewLimiter(rate.Limit(rps), burst))
        if !v.(*rate.Limiter).Allow() {
            c.JSON(429, gin.H{"error": "请求过于频繁"})
            c.Abort()
            return
        }
        c.Next()
    }
}

匿名请求按 IP 当 key,登录用户按 uid,避免单 IP 下多用户互相挤。

4. 分布式限流

多实例部署时每个进程各自一个桶,总限额被实例数放大。用 Redis 做全局令牌桶,lua 脚本保证原子:

-- KEYS[1]=limiter key, ARGV[1]=now ms, ARGV[2]=rate, ARGV[3]=burst
local tokens = tonumber(redis.call('get', KEYS[1]) or ARGV[3])
local ts = tonumber(redis.call('get', KEYS[1]..':ts') or ARGV[1])
local delta = math.min(ARGV[3], tokens + (ARGV[1]-ts)/1000*ARGV[2])
if delta < 1 then return 0 end
redis.call('set', KEYS[1], delta-1)
redis.call('set', KEYS[1]..':ts', ARGV[1])
return 1

返回 1 放行、0 拒绝。lua 在 Redis 单线程执行,多实例并发不会超发。

5. Go 侧调用

func allow(key string, rps, burst float64) bool {
    now := float64(time.Now().UnixMilli())
    res, _ := rdb.Eval(ctx, luaScript, []string{key}, now, rps, burst).Int()
    return res == 1
}

key 拼上用户或 IP,所有实例共享同一计数。

6. 返回标准限流头

告诉客户端还剩多少额度:

c.Header("X-RateLimit-Limit", fmt.Sprint(burst))
c.Header("Retry-After", "1")

前端拿到 429 后按 Retry-After 退避,别死循环重试。

7. 上线清单

限流阈值按压测定,别拍脑袋。全局限流按实例数摊薄。分布式限流 key 设计要能识别用户又不泄隐私。被限流的请求返 429 而非 500,别污染错误监控。静态资源和健康检查不限流。

下一篇讲熔断和重试,保护对下游的调用。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/190