高并发限流需按场景选型:令牌桶适配突发流量,漏桶适合匀速控制,sentinel+nacos实现分布式统一流控;自研内存限流易出竞态、扩容失效,应禁用。

高并发场景下,Gin 限流中间件不能只看“能不能用”,得看「令牌桶是否支持突发、漏桶是否压得住毛刺、Sentinel 是否扛得住分布式扩缩容」——单机内存计数器在压测时掉 token 是常态,而 uber-go/ratelimit 的漏桶默认每秒只放行 1 次,根本撑不起真实 API 网关的 QPS。
令牌桶中间件:为什么 NewLimiter 手动实现容易丢令牌
手动写的令牌桶(比如用 time.Now().Unix() 做时间判断)在高并发下会因竞态导致 tokens 字段被多个 goroutine 同时读写,出现“明明该有令牌却返回 false”的现象。更隐蔽的问题是:如果没加锁或没用 atomic,next 时间戳可能被覆盖,造成整桶重置逻辑失效。
- 必须用
sync.Mutex或atomic.Int64保护tokens和next -
rate单位要统一为「每秒令牌数」,别传100ms这类非整秒值,否则next计算错位 - 桶容量
capacity建议设为rate * 2左右,太小扛不住突发,太大等于没限流
漏桶中间件:uber-go/ratelimit 的默认行为坑在哪
uber-go/ratelimit 默认是 strict 模式:每次调用 r.Take() 都阻塞到下一个可用时间点,而不是立即返回失败。这会导致请求排队等待,延迟飙升——你以为是限流,其实是隐式排队。线上服务看到 P99 延迟翻倍,八成是这个锅。
- 改用
ratelimit.WithoutSlack()可禁用 slack 补偿,让行为更接近“硬限流” - 别把
ratelimit.New(10)理解成“每秒最多 10 次”,它实际是“每 100ms 放行 1 次”,即固定间隔,不累积 - 若需支持突发,漏桶就不合适;真要用,建议搭配前置队列(如 Redis List),但复杂度陡增
Sentinel-Gin:分布式限流必须配 sentinel-core + sentinel-datasource-nacos
纯内存版 Sentinel(sentinel-golang 默认模式)在多实例部署时,各节点限流阈值独立计算,等同于“总阈值 × 实例数”。你配了 QPS=100,起 4 个 Pod 就变成 400,完全失效。
- 必须接入外部规则源,如
sentinel-datasource-nacos或sentinel-datasource-redis - 系统规则(
SystemRule)比流量规则(FlowRule)更适合防雪崩:它基于 CPU/LOAD/RT 动态调整,不依赖人工预估 QPS -
sentinel-golang的flow.LoadRules是热加载,但规则变更后有 1~2 秒延迟,压测中别指望实时生效
生产环境别碰“自研内存计数器”中间件
网上大量教程用 map[string]int + time.Now() 实现按 IP 或路径限流,这类代码在单机低并发下能跑通,一上生产就出事:goroutine 泄漏、map 并发写 panic、GC 压力暴增。更致命的是——它无法做跨实例协同,扩容即失效。
- IP 维度限流必须走 Redis + Lua 原子脚本,否则同一 IP 请求打到不同实例,限流形同虚设
- 若坚持用内存,至少套一层
sync.Map,且 key 要带时间窗口哈希(如"ip:192.168.1.1:2026081912"),避免无限增长 - 所有内存型限流中间件,上线前必须跑
go tool pprof看 goroutine 数和 heap 分配,超 500 goroutine 就得重构
真正难的不是选算法,而是搞清你的流量毛刺特征:是短时尖峰(选令牌桶),还是持续匀速(漏桶够用),还是多活集群必须统一流控(Sentinel + Nacos)。别在压测最后一天才发现限流中间件自己先成了瓶颈。











