直接用golang.org/x/time/rate实现轻量限流中间件。它线程安全、无锁、精度高,rate.newlimiter(rate.every(time.second/10), 20)表示每秒10次请求、突发容量20;需用reserven而非burst()获取剩余令牌,按ip或路径差异化限流时应结合sync.map与定时清理防内存泄漏。

用 golang.org/x/time/rate 实现轻量限流中间件
直接用 golang.org/x/time/rate 就能跑起来,不用装第三方包。它线程安全、无锁、精度高,适合大多数单机限流场景。
-
rate.NewLimiter(rate.Every(time.Second/10), 20)表示每秒最多 10 次请求(每 100ms 放一个令牌),桶容量为 20,允许短时突发 - 别用
Allow()后再读Burst()当剩余数——Burst()返回的是桶最大容量,不是当前剩余;真要暴露剩余值,得用limiter.ReserveN(time.Now(), 1)拿.Delay()和.OK判断,再算剩余 - 在 Gin 中注册时,确保中间件放在
router.Use()里,而不是单个路由的.GET(..., middleware),否则可能漏掉 OPTIONS 或健康检查路径
按 IP 或路径做差异化限流时怎么避免内存泄漏
每个 X-Real-IP 或 c.Request.URL.Path 都要配独立的 *rate.Limiter,但不能无限缓存。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用
sync.Map存 key →*rate.Limiter映射,读多写少场景下比加锁map更高效 - 必须设过期清理:比如启动 goroutine 定期扫描,对最后访问时间超过 5 分钟的 key 调用
Map.Delete();不清理的话,爬虫扫出成千上万个 IP 就会吃光内存 - 别在
Allow()前对整个 map 加锁——rate.Limiter自身已并发安全,锁只该用在LoadOrStore创建新 limiter 的瞬间
Gin 里集成限流中间件时容易忽略的边界点
限流中间件上线后没报错,但监控发现部分请求没被控住,大概率是这几个地方没对齐。
- 健康检查端点(如
/healthz)必须绕过限流,否则 k8s liveness probe 失败会导致重启循环;建议中间件里先匹配 path,命中就c.Next()直接放行 - Gin 的
c.ClientIP()在反向代理后可能拿到的是 Nginx 内网地址,得配合c.Request.Header.Get("X-Real-IP")或"X-Forwarded-For"解析真实 IP - 如果用了
limiter.Wait()而不是Allow(),请求会被阻塞直到有令牌,这在超时敏感接口(如支付回调)里可能引发级联超时,优先选拒绝式逻辑
什么时候该放弃单机限流、改用 Redis 分布式方案
单机限流在多实例部署下天然失效——三个服务实例各自计数,实际总 QPS 是单机的三倍。但上 Redis 不是“越早越好”。
- 先确认是否真需要分布式:如果服务只部署一个实例,或限流粒度是全局(非 per-user/IP),
rate.Limiter完全够用 - Redis 方案会引入额外延迟(每次请求多一次网络 RTT)和故障面(Redis 挂了限流器就失效),建议搭配降级开关,比如用
atomic.Bool控制是否启用 Redis 限流 - 别手写 Lua 脚本实现滑动窗口——用
go-redis/redis_rate这类成熟封装,它已处理好原子性、时钟漂移和连接复用问题
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










