<p>Echo 中间件用 rate.Limiter 限流需注意:正确配置 rate.NewLimiter(rate.Limit(10), 10)、自定义 KeyFunc 校验非空 API Key 或 JWT 用户 ID、启用 EnableHeader 写入 X-RateLimit-* 响应头、定期清理 sync.Map 中过期 key 防内存泄漏。</p>

中间件里用 rate.Limiter 做请求限流最直接
Echo 自带的 middleware.RateLimiter 底层基于 golang.org/x/time/rate,开箱即用,不用自己手写令牌桶逻辑。它默认按 IP 限流,适合大多数 API 网关场景。
常见错误是直接传入固定 rate.Limit(10) 却没配 rate.Every(time.Second),导致限流器实际每秒只允许 1 次请求(因为默认 Every 是 0)。
- 正确写法:
rate.NewLimiter(rate.Limit(10), 10)表示「桶容量 10,每秒补充 10 个 token」 - 若想实现「每分钟最多 60 次」:用
rate.NewLimiter(rate.Every(time.Minute/60), 60) - 注意
rate.NewLimiter第二个参数是 burst(突发容量),不能小于 limit,否则会 panic
按用户 ID 或 API Key 限流需自定义 keyFunc
默认按 c.RealIP() 限流,但业务常需按 X-API-Key 或 JWT 中的 sub 字段区分。这时必须重写 middleware.KeyFunc,否则所有请求都挤在同一个桶里。
典型踩坑:从 c.Request().Header.Get("X-API-Key") 取值后没做空判断,导致空 key 被统一归为 " ",整个服务被一个无效 key 打爆。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 务必校验 header 值非空且长度合理:
strings.TrimSpace(key) != "" && len(key) >= 12 - 若用 JWT,建议在中间件中提前解析并缓存到
c.Set("user_id", userID),再在KeyFunc中取:c.Get("user_id").(string) - 避免在
KeyFunc里做耗时操作(如查 DB),否则限流本身成性能瓶颈
middleware.RateLimiter 返回 429 但前端收不到 X-RateLimit-* 头
这个中间件默认不写响应头,而很多监控或 SDK 依赖 X-RateLimit-Remaining 等字段做降级判断。必须手动启用头写入,且注意顺序——它要在限流中间件之后、handler 之前执行。
- 启用方式:设置
middleware.RateLimiterConfig.EnableHeader = true - 头字段名可自定义,但别改默认值,否则和标准不兼容:
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset - 如果用了
echo.HTTPErrorHandler全局错误处理,要确保它不覆盖限流中间件已写的 status code 和 headers
高并发下内存泄漏风险来自未清理的 map key
middleware.RateLimiter 内部用 sync.Map 缓存每个 key 对应的 limiter 实例。但 Echo 不提供自动过期机制,长期运行后可能积累大量僵尸 key(比如临时 token、一次性 webhook 回调地址)。
这不是 bug,而是设计权衡:官方认为业务侧更清楚 key 生命周期。所以你得自己加清理逻辑。
- 简单方案:用
time.Ticker每 5 分钟遍历sync.Map,删掉 30 分钟无访问的 key(需配合时间戳记录) - 更稳妥:换用外部存储(如 Redis +
github.com/go-redis/redis_rate),天然支持 TTL 和分布式共享桶 - 千万别用
map[string]*rate.Limiter替代sync.Map,并发读写会 crash

















