正确做法是全局复用rate.Limiter实例或按key用sync.Map缓存,避免每次请求新建;集群需用Redis+Lua实现原子滑动窗口限流,并暴露指标、标准响应头及保障健康检查不被限流。

用 rate.NewLimiter 做单机限流,别在 handler 里 new 实例
绝大多数 Golang 微服务的入口限流,直接用 golang.org/x/time/rate 就够了——它轻量、线程安全、支持 context 取消,且已深度融入标准生态。但常见错误是:每次 HTTP 请求都 new 一个 rate.Limiter,导致限流失效(每个实例独立计数,毫无约束力)。
- 正确做法:全局复用一个实例(如网关层总 QPS 限制),或按 key(如
X-User-ID)用sync.Map缓存,每个 key 对应一个*rate.Limiter -
rate.Every(100*time.Millisecond)表示每 100ms 放 1 个令牌,等价于 10 QPS;burst不宜设为 1,建议 ≥3,否则突发流量全被拒,体验差 - 别用
Allow()+time.Sleep模拟等待——该方法非阻塞,返回 false 就该立刻拒绝;真要等,用limiter.Wait(ctx),但务必确保ctx已设超时,否则可能永久挂起
按用户/IP/路径做差异化限流,用 sync.Map + 过期清理
生产中几乎从不限“全站 QPS”,而是分维度控制:比如普通用户每分钟 60 次,VIP 用户 300 次,健康检查接口(/health)完全放行。这就需要为不同标识动态创建并复用限流器,同时防内存泄漏。
- 用
sync.Map存map[string]*rate.Limiter,key 可以是"user:" + userID或"ip:" + realIP - 不能任由 key 无限增长:对每个新 key,启动一个
time.AfterFunc(30 * time.Minute, func() { syncMap.Delete(key) })清理闲置限流器 - 避免在高并发下频繁写
sync.Map:采用“读多写少”策略,先Load,命中则直接用;未命中再LoadOrStore,不查表就Store - 注意:Gin 中提取
X-Real-IP要配TrustedProxies,否则拿到的是 Nginx 内网地址,限流失效
集群部署必须上 Redis + Lua,别信“本地缓存同步”
单机 rate.Limiter 在多副本场景下完全无效——5 个实例各放行 100 QPS,实际就是 500 QPS,系统照样被打垮。此时必须依赖外部共享状态,Redis 是事实标准,但实现方式很关键。
- 固定窗口(Fixed Window)脚本最简,但有临界突刺问题;滑动窗口(Sliding Window)更准,推荐用 Redis
ZSET+ZCOUNT统计最近 N 秒请求数 - 必须用 Lua 脚本保证原子性:
ZADD+ZCOUNT+ZREMRANGEBYSCORE三步不能拆,否则并发下计数错乱 - 别用
go-redis的 pipeline 模拟原子操作——网络延迟和重试会让结果不可靠;Lua 是唯一靠谱选择 - 连接池要调大(
PoolSize: 50),否则限流逻辑本身成瓶颈;同时设置Timeout: 100ms,超时直接放行或拒绝,不拖慢主流程
限流不是加个中间件就完事,必须暴露指标和标准响应头
上线后没人知道限流是否生效、谁被限、为什么限——直到告警炸了才去翻日志。真正的限流落地,必须把可观测性嵌进逻辑里。
mallcloud商城基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离vue的企业级微服务敏捷开发系统架构。并引入组件化的思想实现高内聚低耦合,项目代码简洁注释丰富上手容易,适合学习和企业中使用。真正实现了基于RBAC、jwt和oauth2的无状态统一权限认证的解决方案,面向互联网设计同时适合B端和C端用户,支持CI/CD多环境部署,并提
立即学习“go语言免费学习笔记(深入)”;
- 每次触发限流,打结构化日志:含
reason="user_burst"、limit=60、remaining=0,方便 ELK 聚类分析 - 用 Prometheus 上报
http_requests_limited_total{route="/api/user", reason="ip"},配合 Grafana 看板监控限流率突增 - HTTP 响应必须带标准头:
Retry-After: 60(秒级退避)、X-RateLimit-Limit: 60、X-RateLimit-Remaining: 0,客户端才能智能降频 - 最关键一点:健康检查端点(如
/health)绝对不能被限流中间件包裹,否则 K8s liveness probe 失败,触发误重启
限流真正难的不是算法,而是怎么让每个限流决策可追溯、可配置、可联动——比如熔断器处于 Open 状态时,主动把对应 key 的限流阈值临时归零,而不是等请求进来再拒绝。









