Fiber的limiter中间件默认不生效,因其使用非线程安全的memory.New内存存储,多实例部署时各副本计数独立;生产环境必须替换为redis.New等共享后端,并合理配置Max、Expiration、KeyGenerator与Skipper。

为什么 Fiber 的 limiter 中间件默认不生效
因为 Fiber 的 limiter.New 默认使用内存存储(memory.New),而该实现**不是线程安全的**,在多 Worker 或多进程部署(如用 fork、pm2、Docker 多副本)时,每个实例维护独立计数器,完全无法限制全局请求量。现象是:压测时 QPS 轻松突破阈值,日志里看不到拒绝记录。
必须显式替换为共享存储后端,否则限流形同虚设:
- 单机多协程(无 fork / 无副本)→ 可临时用
memory.New,仅用于本地调试 - 生产环境 → 必须用
redis.New,配合 Redis 实例(推荐 Redis 6+,支持 Lua 原子操作) - 若无 Redis,可用
ring.New(基于环形缓冲区,内存占用低,但仍是单实例,适合轻量级 API 网关)
limiter.New 的关键参数怎么配才防刷
核心不是“设个数字”,而是匹配真实攻击模式。恶意刷量常见三种节奏:短时高频(秒级爆破)、长时匀速(爬虫轮询)、突发脉冲(抢购)。对应配置要分层:
-
Max: 100→ 不是全局每秒 100 次,而是「每个 IP 在窗口内最多 100 次」,需配合Expiration -
Expiration: 30 * time.Second→ 窗口设 30 秒比 1 分钟更有效:绕过窗口重置的爬虫更难适应 -
KeyGenerator必须自定义 → 默认只取c.IP(),但代理或 CDN 后的真实 IP 在X-Forwarded-For;不处理会导致全站被同一个出口 IP 封禁 - 加
MiddlewareConfig.Skipper→ 排除健康检查路径(如/healthz)、静态资源(/assets/),避免误杀
示例片段:
l := limiter.New(limiter.Config{
Max: 50,
Expiration: 20 * time.Second,
KeyGenerator: func(c *fiber.Ctx) string {
ip := c.Get("X-Real-IP")
if ip == "" {
ip = c.IP()
}
return ip
},
Skipper: func(c *fiber.Ctx) bool {
return strings.HasPrefix(c.Path(), "/healthz") ||
strings.HasPrefix(c.Path(), "/assets/")
},
})
Redis 后端连不上或响应慢的典型原因
redis.New 依赖 github.com/go-redis/redis/v8,但 Fiber 官方示例常省略连接池和超时控制,导致生产环境频繁超时或连接耗尽:
- 未设置
ContextTimeout→ Redis 故障时,请求卡在中间件,拖垮整个服务 - 未复用
*redis.Client→ 每次新建 client 会创建新连接池,内存泄漏 + TIME_WAIT 爆满 - Redis 密码或地址写错 → 日志只报
"rate limit: redis error",不带具体错误,需捕获limiter.RedisStore初始化返回的 error - AOF 持久化开启且磁盘慢 → 建议关闭 AOF,限流数据本就不需持久化
正确做法:全局初始化一次 client,传入 store:
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: os.Getenv("REDIS_PASS"),
DB: 2,
ContextTimeout: 300 * time.Millisecond, // 关键!
})
store := redis.New(rdb)
l := limiter.New(limiter.Config{...}, limiter.WithStore(store))
如何验证限流是否真起作用
别只看 HTTP 状态码 429,很多团队漏掉两个关键点:指标可观测性、客户端行为反馈。
- 在中间件后加日志钩子:
c.Locals("limiter_remaining")和c.Locals("limiter_reset")可读取剩余次数和重置时间戳,打点到 Prometheus(暴露为http_requests_limited_total) - 前端调用失败时,应检查响应头
X-RateLimit-Remaining和X-RateLimit-Reset,而不是只弹 “请求太频繁”;否则用户刷新页面又触发,陷入死循环 - 用
ab -n 200 -c 50 http://localhost:3000/api/submit压测,观察Failed requests是否稳定在预期比例(如设 50/20s,压 200 次应有 ≈100 次失败)
真正难的是动态策略:比如登录用户放宽到 200 次/分钟,游客压到 10 次/分钟——这需要把 KeyGenerator 改成拼接 userID 或角色,而不是硬编码 IP。


















