rate.limiter是go单机限流最直接可靠的选择,因其基于纳秒级单调时钟的令牌桶实现、线程安全、低开销且经高并发验证;手写ticker或计数器易在突发流量或窗口切换时失效。

rate.Limiter 是 Go 微服务中做单机请求限速最直接、最可靠的选择,绝大多数场景不需要自己实现或引入 Redis —— 除非你明确需要跨实例全局控流。
为什么不用 time.Ticker 或手写计数器
用 time.Ticker 做限流本质是固定周期“滴答”,它不感知请求实际发生时间,也无法处理突发流量:比如 1 秒内来了 15 次请求,Ticker 只能保证每秒 tick 一次,完全无法拦截超额;手写计数器(尤其是固定窗口)在窗口切换瞬间可能放行双倍请求,且多 goroutine 并发下容易漏判或 panic。而 rate.Limiter 基于纳秒级单调时钟动态补桶,线程安全,耗时仅 10–50ns,已通过高并发压测验证。
Limit 和 Burst 怎么设才不翻车
Limit 是长期平均速率(单位:token/秒),不是“每秒固定给 N 个”;Burst 是桶容量,必须 ≥ Limit,否则新速率永远达不到(比如 Limit=10 但 Burst=5,桶最多存 5 个 token,再快也白搭)。
- 常见误配:
rate.NewLimiter(10, 1)→ 突发抖动全被拒,健康检查都可能失败 - 合理值:Burst 通常取平均 QPS 的 2–3 倍,如平均 50 QPS,设为
100或150 - 动态调速时,必须同时调
SetLimit()和SetBurst(),否则旧 Burst 会卡住新 Limit - 注意:
rate.Every(100 * time.Millisecond)≈ 10 QPS,别把 Every 当 QPS 直接填数字
HTTP 中间件里 Allow() 和 Wait() 到底怎么选
HTTP 接口几乎都应该用非阻塞方式,避免请求卡住:
-
Allow()立刻返回 bool,适合快速拒绝,但它不重置桶,连续高频调用可能让桶瞬间耗尽 - 一次请求消耗多个 token(如文件上传分片)时,应改用
ReserveN(time.Now(), n)预占,再显式判断.OK()或.Cancel() -
Wait(ctx)会阻塞直到拿到 token 或超时,务必传带 deadline 的context.Context,否则客户端断连后 goroutine 可能永久挂起 - 示例:用
ctx, cancel := context.WithTimeout(c.Request.Context(), 200*time.Millisecond)包一层再传给Wait()
按 IP 或用户 ID 限流时容易踩的坑
不能靠单个全局限速器,必须为每个 key(如 IP、userID)维护独立 *rate.Limiter 实例:
- 别用
r.RemoteAddr直接当 key —— 经过 Nginx 或云 WAF 后全是同一个地址;要解析X-Forwarded-For,但只信任可信代理网段(如"10.0.0.0/8")下的最左非私有 IP - key 推荐用
net.ParseIP(ipStr).To16().String(),统一 IPv4/IPv6 格式,避免哈希冲突 - 用
sync.Map缓存时,必须加 TTL 清理:启动 goroutine 定期扫描,删除 5–30 分钟无访问的条目,否则内存持续增长 - 高并发下可考虑哈希分片(如按 IP 低字节 % 8 分到 8 个
sync.Map),缓解单 map 锁竞争
真正难的不是写限流逻辑,而是 key 的归一化和生命周期管理 —— IP 解析错、缓存不清理、burst 设得过大又没监控,这些细节比算法本身更容易让限流失效或伤及正常流量。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











