

新闻资讯
技术教程用time.Ticker实现固定窗口限流简单但易超限,因窗口切换存在竞态和时钟漂移;推荐使用golang.org/x/time/rate的漏桶模型,支持突发、线程安全且性能优;分布式场景需Redis等外部存储协调。
time.Ticker 做固定窗口限流,简单但容易超限固定窗口限流最直观:每秒最多处理 N 次请求,到整秒重置计数器。但问题在于边界——比如 0.9s 到 1.1s 这 200ms 内,可能触发两个窗口的计数(0s 窗口剩 1 次 + 1s 窗口刚清零),实际通过 2×N 次请求。
用 time.Ticker 配合原子计数器能快速验证逻辑,但不适合生产环境的精度要求:
var (
limit = 10
count int64
mu sync.RWMutex
)
ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
mu.Lock()
count = 0
mu.Unlock()
}
}()
// 在 handler 中:
mu.RLock()
c := atomic.LoadInt64(&count)
mu.RUnlock()
if c >= int64(limit) {
http.Error(w, "rate limited", http.StatusTooManyRequests)
return
}
atomic.AddInt64(&count, 1)
count++ 会出错;必须用 atomic 或 sync.Mutex
time.Ticker 不保证严格准时,尤其在 GC 或系统负载高时会有漂移golang.org/x/time/rate 实现平滑漏桶标准库扩展包 rate.Limiter 是 Go 官方推荐方案,底层是“漏桶”模型:以恒定速率向桶中“漏水”,每次请求需先“取水”。它支持突发(burst)和平均速率(rps),且线程安全、无锁路径优化好。
关键参数含义:
立即学习“go语言免费学

rate.Every(100 * time.Millisecond) 表示每 100ms 放行 1 次 → 等价于 10 rpsburst = 5 表示桶容量为 5,允许短时突发 5 次请求Wait() 会阻塞,TryConsume() 则立即返回布尔值import "golang.org/x/time/rate"var limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 5)
func handler(w http.ResponseWriter, r *http.Request) { if !limiter.TryConsume(1) { http.Error(w, "too many requests", http.StatusTooManyRequests) return } // 处理业务逻辑 }
注意:TryConsume(1) 中的 1 是“令牌数”,一般单请求消耗 1;若接口权重不同(如上传消耗 3),可动态传入。
把限流逻辑抽成 HTTP 中间件,复用性更高,也便于统一响应头(如 X-RateLimit-Remaining)。
常见错误是把 rate.Limiter 实例定义在函数内,导致每次请求新建一个 limiter,完全失效:
*rate.Limiter
limiter := rate.NewLimiter(...) 写在 handler 函数里SetLimit() 或 SetBurst(),有锁开销且非线程安全func RateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.TryConsume(1) {
w.Header().Set("X-RateLimit-Limit", "10")
w.Header().Set("X-RateLimit-Remaining", "0")
http.Error(w, "rate limited", http.StatusTooManyRequests)
return
}
// 更新响应头(剩余令牌数)
remaining := limiter.Burst() - int(limiter.ReserveN(time.Now(), 1).TokensFromBucket())
w.Header().Set("X-RateLimit-Remaining", strconv.Itoa(remaining))
next.ServeHTTP(w, r)
})
}
}
// 使用:
http.Handle("/api/", RateLimitMiddleware(
rate.NewLimiter(rate.Limit(10), 10),
)(http.HandlerFunc(apiHandler)))
rate.Limiter 失效,得换方案rate.Limiter 是纯内存实现,多实例部署时各自维护独立桶,总通过量变成 N × 单机 limit。此时必须引入外部存储做协调:
INCR + EXPIRE 组合)是最常用解法,能保证原子性别低估网络开销——一次 Redis 请求约 0.2~1ms,而本地 TryConsume 是纳秒级。高频小接口加 Redis 限流,可能让 P99 延迟翻倍。
真正需要分布式限流的,往往是网关层(如基于 gin 或 echo 的 API 网关),而不是每个微服务内部自己搞一套。