rate.Limiter是Go官方推荐的单机限流方案,基于纳秒级时间戳动态计算令牌,线程安全、无goroutine开销、耗时10–50ns;初始化用rate.Every避免浮点误差,burst控制突发容量,需配合合理context超时与key管理策略。

rate.Limiter 是唯一推荐的单机限流方案
自己手写计数器、用 time.Ticker + channel 模拟令牌桶、或基于时间窗口的 map 计数,全都不如 golang.org/x/time/rate 可靠。它不是“模拟”,而是按纳秒级时间戳动态计算剩余令牌,无后台 goroutine,Allow() 平均耗时在 10–50 ns 级别。关键优势是:线程安全、不依赖 GC 调度精度、能正确处理瞬时并发堆积(time.Ticker 在 10ms 内涌入 100 请求就会全放行,而 rate.Limiter 会严格按配额拒绝)。
初始化参数怎么设才不翻车
rate.NewLimiter(limit rate.Limit, burst int) 两个参数必须一起理解:
-
limit单位是「每秒令牌数」,直接用rate.Every(200 * time.Millisecond)(= 5 QPS),别手动算1.0 / 0.2浮点值——Go 的rate.Limit是int64类型,浮点误差会导致限速漂移 -
burst是桶容量,也等于首次允许的最大并发请求数;设为 1 就退化成死板匀速,设为 10 表示空闲足够久后可突增 10 次;但别设过大(如 >1000),否则限流形同虚设 - 桶初始状态是满的——所以第一个请求永远通过;若需冷启动防击穿(比如服务刚上线怕流量突袭),得手动调用
limiter.WaitN(ctx, burst)先预占满
按用户/IP 分桶时 sync.Map 不够用
直接用 sync.Map[string]*rate.Limiter 存不同用户的限流器,看似简单,但容易内存泄漏:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- key 来源不能直接用
r.RemoteAddr,代理环境下是内网地址;应解析X-Forwarded-For并校验可信跳数(如只取第 1 跳且 header 由 Nginx 添加) - 没 TTL 清理机制,长期运行后 map 持续膨胀;建议用哈希截断 key(如
md5.Sum([]byte(ip))[:8]),再配合后台 goroutine 定期扫描time.Since(lastUsed) > 1h的 entry 并删除 - 不要每次请求都
new一个rate.Limiter——它本身轻量,但高频创建+GC 会拖慢性能;复用实例即可
Wait() 超时不是 bug,是调用姿势错了
遇到 rate: Wait returned an error: context deadline exceeded,这不是限流器故障,而是你没给足等待时间:
立即学习“go语言免费学习笔记(深入)”;
-
Wait()和WaitN()是阻塞式,会等令牌可用或 ctx 超时;如果 handler 设置了 100ms 超时,但限流器当前令牌耗尽,它就会立即返回该错误 - 正确做法是确保传入的
context.Context有合理超时(如ctx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond)),并在错误时返回http.StatusTooManyRequests - 若业务不允许阻塞(如实时推送接口),改用
Allow()或AllowN()做非阻塞判断,失败则快速降级或返回兜底响应
真正难的从来不是“怎么写一个限流器”,而是决定谁该被限、限多少、限完怎么通知、以及分布式场景下如何对齐配额——这些单靠 rate.Limiter 解决不了,得结合外部存储和业务策略。

















