Fiber默认rateLimiter不能直接上生产,因其仅依赖ctx.IP()导致代理环境下所有用户共用同一限流key,且内存存储不支持多实例共享,易被绕过或误杀。

直接用 fiber 自带的 rateLimiter 中间件就能防基础刷量,但生产环境必须配合 IP 提取、动态 key 和 Redis 后端,否则容易被绕过或误杀。
为什么 fiber 的默认 rateLimiter 不能直接上生产
它默认只认 ctx.IP(),而真实请求常经过 CDN、Nginx 或云 WAF,ctx.IP() 返回的是代理 IP(如 10.0.0.1),所有用户会被聚合成同一个限流 key。另外,内存存储(memory)不共享,多实例部署时完全失效。
常见错误现象:
– 同一用户在不同机器访问,限流不生效
– 所有用户共用一个计数器,正常用户被连带封禁
– 换个请求头(如加 X-Forwarded-For)就绕过限制
- 务必重写
getIPFunc,优先从X-Real-IP或X-Forwarded-For提取,再 fallback 到ctx.IP() - 禁用默认
memory存储,改用redis实例(推荐github.com/go-redis/redis/v9) - 不要信任前端传来的任何标识(如
user_id),IP + User-Agent 组合仍不够稳,建议加设备指纹或 token 签名校验层
如何用 Redis 实现滑动窗口限流(非固定窗口)
fiber 官方 rateLimiter 只支持固定窗口(如“每分钟最多 10 次”),无法解决窗口切换瞬间的漏判问题。要真正防暴力破解类攻击,得自己实现滑动窗口逻辑,核心是用 Redis 的 ZSET 存储时间戳并自动剔除过期项。
关键点:
– 每次请求写入 ZADD key timestamp timestamp,score 用毫秒时间戳
– 用 ZCOUNT key (now-window_ms) +inf 统计当前窗口内请求数
– 设置 EXPIRE key window_sec + 5 防 key 泄露堆积
- 窗口大小建议设为 60 秒,但统计精度到毫秒,避免整秒边界突增
- key 命名格式建议:
rl:${ip}:${path}或rl:${ip}:${ua_hash},防止路径参数污染 - 注意
ZCOUNT的左开右闭区间:(min, max],所以 min 要用now - window再减 1 毫秒
如何区分恶意爬虫和真实用户
单纯靠频率不够——有些爬虫控制得很“温柔”,单 IP 每秒只发 2–3 次;而抢购场景下真实用户也可能短时高频。得叠加行为特征判断。
可落地的轻量策略:
– 检查 User-Agent 是否为空、含 bot/crawler、或为已知低质 UA(如 python-requests)
– 校验请求头是否缺失 Accept、Accept-Language 等浏览器必带字段
– 对高风险路径(如 /api/login)额外要求携带 X-Fingerprint(前端 JS 生成的简单设备哈希)
- 别依赖前端 JS 生成的指纹做唯一标识,它可被复现;仅作辅助判断,和 IP+UA 一起构成二级 key
- 对命中 UA 黑名单的请求,直接返回
403,不进限流逻辑,节省 Redis QPS - 记录
X-Forwarded-For全链路(如a.b.c.d, e.f.g.h),取第一个非内网地址,但需提前配置可信代理段(10.0.0.0/8,172.16.0.0/12)
Redis 连接与降级怎么不拖垮主服务
限流模块一旦 Redis 不可用,默认应放行(fail-open),而不是阻塞或报错——否则一次 Redis 故障就会导致全站接口不可用。
实操要点:
– 初始化 Redis client 时设置 Timeout、ReadTimeout、WriteTimeout 均 ≤ 100ms
– 使用 context.WithTimeout 包裹每个 Redis 调用,超时直接 return false(即不限流)
– 在中间件里加本地缓存兜底:用 sync.Map 缓存最近 1000 个 IP 的“最近一次被限流时间”,5 秒内重复触发直接拒绝(不查 Redis)
- 本地缓存只用于快速拦截,不替代 Redis;它的 key 是
ip:ts,value 是毫秒时间戳,TTL 固定 5 秒 - 不要在限流中间件里做日志打点(如
log.Printf),高频场景下 I/O 会成为瓶颈;用异步 channel + worker 收集指标 - 监控项必须有:
redis_rate_limit_errors_total、rate_limit_bypassed_total(fail-open 次数)、local_cache_hit_ratio
滑动窗口的精度和 Redis 延迟强相关,如果 P99 延迟超过 50ms,窗口滑动的实际效果会明显劣化;与其硬扛,不如在入口层(如 Cloudflare 或 EdgeOne)先做粗粒度 CC 防护,把最野的流量挡在外面。











