Fiber框架需自行集成限流,推荐用golang.org/x/time/rate实现:复用rate.Limiter实例、按路径或IP分组、在ctx.Next()前校验;官方fiber/middleware/limiter支持Redis但需自定义KeyGenerator,高并发下注意sync.Map内存清理与Redis原子操作。

Fiber 框架本身不内置限流中间件,但可以通过 fiber/adaptor 兼容标准 net/http 限流库,或直接集成轻量级限流器(如 golang.org/x/time/rate)实现可靠限流——关键在于把限流逻辑放在路由匹配前、且避免在并发高时因共享状态引发竞争。
用 rate.Limiter 手写基础限流中间件
最可控的方式是基于 golang.org/x/time/rate 自定义中间件。它轻量、无依赖、支持每秒/每分钟粒度,且 rate.Limiter 本身是并发安全的。
常见错误是把 rate.NewLimiter 实例按请求新建,导致完全失效;必须复用同一个实例(按路径或 IP 等维度分组创建)。
- 每个路由路径单独配一个
rate.Limiter:适合固定接口 QPS 差异大(如/health放宽,/login从严) - 按客户端 IP 提取后做哈希分桶,再查对应
rate.Limiter:适合防单 IP 暴力刷 - 避免在
ctx.Next()后检查限流结果——必须在进入业务逻辑前调用limiter.AllowN(time.Now(), n)并根据返回值提前中断
示例(按路径限流):
func RateLimitByPath(rps int) fiber.Handler {
limiters := sync.Map{} // path → *rate.Limiter
return func(c *fiber.Ctx) error {
path := c.Path()
limiter, ok := limiters.Load(path)
if !ok {
limiter = rate.NewLimiter(rate.Limit(rps), rps)
limiters.Store(path, limiter)
}
if !limiter.(*rate.Limiter).Allow() {
return c.Status(fiber.StatusTooManyRequests).SendString("too many requests")
}
return c.Next()
}
}使用 fiber/middleware/limiter 官方扩展
Fiber 官方维护的 fiber/middleware/limiter 是封装好的限流中间件,底层也用 rate.Limiter,但额外支持内存/Redis 存储策略和更灵活的 key 生成规则。
容易踩的坑是默认配置下所有请求共用一个桶(key 固定为 "global"),没做区分就等于全局限流,误伤正常流量。
- 务必通过
limiter.Config.KeyGenerator自定义 key,比如用c.IP()或c.Get("X-Forwarded-For")提取真实 IP - 若用
limiter.NewStoreRedis,注意 Redis 连接池超时和网络抖动会导致限流失效(降级为允许通行),需配合熔断或本地 fallback -
limiter.Config.Max是「窗口内最大请求数」,limiter.Config.Expiration是窗口时长(如time.Minute),不是每秒速率
示例(按 IP 限流):
app.Use(limiter.New(limiter.Config{
Max: 100,
Expiration: 1 * time.Minute,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
}))高并发下避免锁竞争与内存泄漏
当按 IP 或用户 ID 动态创建大量 rate.Limiter 实例时,sync.Map 查找快,但长期累积未清理的 key 会吃光内存——尤其代理层透传了随机 X-Forwarded-For 时。
实际部署中,真正需要持久化限流状态的场景极少;多数只需滑动窗口或令牌桶的瞬时控制,无需永久存储。
- 不要用
map[string]*rate.Limiter+mutex,sync.Map足够,但得定期清理过期项(比如用time.Ticker扫描 lastUsed 时间戳) - 如果用 Redis 存储,避免对每个请求都执行
INCR + EXPIRE两步——改用 Lua 脚本原子操作,否则窗口边界可能错乱 - 测试时用
ab -n 1000 -c 200容易触发 false positive(连接复用干扰),建议用hey -z 10s -q 50 -c 50更贴近真实负载
限流真正的复杂点不在代码几行,而在于 key 的语义是否匹配业务意图(是限制“这个 API”,还是“这个用户调这个 API”,还是“这个 IP 调所有 API”),以及降级策略是否明确——比如 Redis 不可用时,是拒绝全部请求,还是放行并打日志?这些必须在中间件外显式定义,不能依赖库默认行为。


















