限流中间件应使用 fiber.new().use() 而非直接在 fiber.new() 初始化时全局注入,因后者会导致静态文件、404等非关键路由也被限流;正确做法是按需对路由组(app.group("/api").use())或单接口(app.get(path, limiter, handler))挂载,且生产环境必须使用官方 github.com/gofiber/fiber/v2/middleware/limiter。

限流中间件该用 fiber.New() 还是 fiber.New().Use()?
直接在 fiber.New() 初始化时传入限流中间件,会导致所有路由(包括静态文件、404)都被限流,这是多数人踩的第一个坑。正确做法是只对需要保护的路由组或具体路由调用 .Use() 或 .Get() 等方法时链式挂载。
- 全局限流(谨慎):在
fiber.New(fiber.Config{})的第二个参数传入中间件 —— 仅适用于 API 全量受控场景 - 按路由组限流:用
app.Group("/api").Use(rateLimiter),最常用且安全 - 单接口限流:直接
app.Get("/user/profile", rateLimiter, handler),适合高敏感操作(如登录、发短信)
fiber/throttle 和 github.com/gofiber/fiber/v2/middleware/limiter 哪个更可靠?
github.com/gofiber/fiber/v2/middleware/limiter 是 Fiber 官方维护的限流中间件,v2.49+ 已内置,而 fiber/throttle 是第三方非官方包,API 不稳定、不支持内存外存储(如 Redis),且文档缺失。生产环境必须用官方 limiter。
- 初始化时默认使用内存存储(
memory),重启后计数清零,适合单实例开发或测试 - 需持久化或集群限流?必须替换为
redis存储,传入limiter.Config{Store: redis.New(...)} - 注意:Redis 存储要求 key 命名唯一,
keyGenerator函数里别直接用c.IP()—— NAT 环境下会误伤多个用户
如何基于 IP + 路径组合做精准限流?
单纯按 IP 限流容易被共享出口(如企业内网、运营商 NAT)误封;只按路径又无法防单用户暴力刷接口。真实场景应组合二者,用 limiter.Config.KeyGenerator 自定义 key:
limiter.New(limiter.Config{
Max: 100,
Period: 1 * time.Hour,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP() + ":" + c.Path()
},
})
-
c.IP()默认走X-Forwarded-For,反向代理未透传时会变成127.0.0.1—— 需提前配置fiber.Config{ProxyHeader: "X-Forwarded-For"} - 如果路径含动态参数(如
/user/123),建议先用正则 normalize 成/user/:id,否则每个 ID 都算独立 key,失去限流意义 - 避免在
KeyGenerator中做耗时操作(如 DB 查询),否则拖慢整个请求链路
返回 429 时怎么让前端知道还能等多久?
官方 limiter 默认只返回 429 Too Many Requests,但没带 Retry-After 头,前端无法智能退避。必须手动启用 Next 和响应头写入逻辑:
limiter.New(limiter.Config{
Max: 30,
Period: 30 * time.Second,
Message: "Too many requests, try again later",
Next: func(c *fiber.Ctx) bool {
// 可在此跳过健康检查、白名单 IP 等
return c.IP() == "127.0.0.1"
},
LimitReached: func(c *fiber.Ctx) error {
// 手动写 Retry-After(单位:秒)
c.Set("Retry-After", "30")
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "rate limit exceeded",
"retry_after": 30,
})
},
})
-
LimitReached是唯一能干预响应体和 header 的钩子,不用它就只能返回默认纯文本 -
Retry-After值应与Period对齐,不要硬写死 —— 若限流窗口是滑动的(如漏桶),需结合存储层计算剩余等待时间 - 注意:若用了 Redis 存储,
LimitReached里不能再发起 Redis 请求,否则可能引发循环依赖或超时
实际部署时最容易忽略的是存储一致性 —— 单机内存限流在 Kubernetes 多副本下完全失效,而 Redis 方案一旦连接失败,默认行为是放行(fail-open),这在支付类接口中极其危险。必须配合 limiter.Config.OnError 做降级熔断,比如记录告警并返回 503。











