正确做法是用sync.map缓存标准化ip对应的rate.limiter实例,设置合理burst(如5–8)与定时清理策略,结合可信x-forwarded-for解析、连接层超时控制及白名单绕过,形成完整防护链路。

直接用 golang.org/x/time/rate 做 IP 级限流,配合可信代理 IP 解析和连接层超时控制,是当前最稳、最轻量、生产验证过的组合方案。别碰手写计数器、别一上来就上 Redis——90% 的场景单机 rate.Limiter 就够用,关键是怎么用对。
怎么给每个 IP 绑定独立的 rate.Limiter
全局只建一个 rate.Limiter 是最大误区,所有 IP 共享额度等于没限;但为每个新 IP 都 new 一个又不清理,sync.Map 会持续膨胀。正确做法是:
- 用
sync.Map存储map[string]*rate.Limiter,key 是标准化后的客户端 IP(不是r.RemoteAddr) - 每个 IP 对应一个
rate.NewLimiter(rate.Every(200*time.Millisecond), 3):表示平均 200ms 放一个请求,允许最多积压 3 次突发 - 设置定时器(比如每分钟)遍历
sync.Map,对 30 分钟内无访问的 IP 调用Delete清理 - 避免在 handler 内部调用
limiter.Allow()——它不阻塞,高并发下可能瞬间打穿阈值;改用limiter.Wait(ctx),传入带 deadline 的 context
为什么不能直接用 r.RemoteAddr 当作真实 IP
r.RemoteAddr 在有 Nginx、Cloudflare 或 K8s Ingress 的场景下,几乎总是反向代理或负载均衡器的内网地址,不是用户真实出口 IP。硬编码取它会导致全站限流失效或误杀 NAT 后大量用户。
真正可信任的只有你自己的反向代理加的 header,而且必须满足两个条件:Nginx 配了 set_real_ip_from + real_ip_header X-Real-IP。Go 层要做的是:
- 只从
X-Forwarded-For头里取最后一个非内网地址(用net.ParseIP+net.Contains判断是否属于"10.0.0.0/8"、"172.16.0.0/12"、"192.168.0.0/16") - 忽略所有来自非可信网段的
X-Forwarded-For,防止伪造 - fallback 到
r.RemoteAddr时,先用strings.Split(r.RemoteAddr, ":")[0]提取 IP,再用net.ParseIP格式化(兼容 IPv6 方括号)
连接层防御比应用层限流更早生效
CC 攻击不全是“高频请求”,还有 slowloris 类型的慢速连接轰炸:建连后只发部分 header,长期不发 body,吃光 http.Server.MaxConns。这时候路由中间件还没执行,限流已经晚了。
必须在 http.Server 初始化阶段就设好连接级防护:
-
ReadHeaderTimeout: 3 * time.Second—— 只约束 header 解析阶段,比ReadTimeout更精准,防半截请求 -
IdleTimeout: 90 * time.Second—— 强制回收空闲连接,避免长连接堆积 -
MaxConnsPerHost设为 50(如果用了httputil.NewSingleHostReverseProxy),防代理层连接耗尽 - Linux 内核参数同步调优:
net.core.somaxconn=4096、net.ipv4.tcp_max_syn_backlog=4096,否则内核日志会出现possible SYN flooding
为什么 burst 设成 1 或 100 都是错的
burst 不是“每秒最多放行数”,而是“令牌桶最大容量”,代表允许的瞬时透支能力。设太小(如 1)会导致健康检查探针、浏览器重试、网络抖动全部被拒;设太大(如 100)等于放行一次洪水,失去限流意义。
合理取值取决于你的 handler 平均耗时和部署规模:
- 若 handler 平均耗时 150ms,单核稳定支撑约 6–7 QPS,
burst可设为 5–8 - 若接口是登录类敏感路径,建议按
ip + path哈希分桶,避免 /login 被刷垮后连带影响 /static 下的资源 - 永远绕过白名单路径:
/healthz、/metrics、/readyz,否则 K8s 会反复重启 Pod - 别在限流中间件里做耗时操作(如解析完整 request body 或查 DB),所有判断必须在
http.Handler执行前完成
真正难的不是写限流逻辑,而是把 IP 解析、burst 设置、连接超时、白名单绕过这四件事串成一条不漏气的链路——漏掉任意一环,攻击者都能绕过去。生产环境上线前,务必用 ab -n 1000 -c 200 http://your-api/login 实测连接堆积和拒绝率,而不是只看单元测试通过。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











