应按IP+路径分桶限流,burst设3–5、rps据handler耗时(如200ms对应5rps)合理设定,必须用Wait(ctx)并配3秒超时,连接层需设ReadHeaderTimeout和MaxConnsPerHost防slowloris。
用 rate.Limiter 按 IP + 路径限流,但 burst 和 rps 必须合理配比
单纯在 http.handler 里套个 rate.newlimiter(10, 10) 拦不住 cc 攻击——这表示“允许突发 10 次,之后每秒匀速放行 10 次”,攻击者只要每秒发 11 次,桶就持续溢出,限流形同虚设。
真实拦截必须满足三个条件:
- 限流维度是
ip + request.URL.Path,避免单个恶意路径耗尽全局桶 -
burst值应略高于该路径的正常并发峰值(比如 3–5),绝不能设成 100+,否则等于主动放行洪水 -
rps要结合 handler 平均耗时估算:若处理一次请求平均耗时 200ms,单核稳撑上限约 5 rps;设 100 就会引发排队雪崩 - 必须调用
limiter.Wait(ctx),而非Allow()—— 后者不阻塞,超限请求仍会进入 handler 占用 goroutine
连接层设 ReadHeaderTimeout,防 slowloris 类慢速攻击
CC 不只是高频请求,还包括 slowloris 这类“只发半个 HTTP 头、长期不发 body”的连接轰炸。等请求进到路由层再限流,连接数早已被占满。
关键配置在 http.Server 实例上:
-
ReadHeaderTimeout: 2 * time.Second—— 只约束 header 解析阶段,比ReadTimeout更精准,2–3 秒足够正常客户端完成 handshake -
MaxConnsPerHost必须显式设(如 50),尤其用httputil.NewSingleHostReverseProxy时,否则代理层连接无上限 - 搭配内核参数:
net.core.somaxconn≥ 4096,net.ipv4.tcp_max_syn_backlog同步加大,防止半连接队列溢出
识别连接指纹而非依赖 User-Agent,避免被轻易绕过
靠 User-Agent 或 Referer 黑名单拦 IP,攻击者用 curl 或伪造头就能绕过。生产环境有效的识别,得基于连接行为本身。
立即学习“go语言免费学习笔记(深入)”;
建议在 net.Listener 接收连接后、交由 http.Server.Serve 前做轻量级指纹提取:
- 记录
net.Conn.RemoteAddr().IP+tls.ConnectionState.ServerName(HTTPS 场景) - 对同一指纹,统计 10 秒内新建连接数;超过阈值(如 15 次)直接
conn.Close() - 避免解析完整 HTTP body 或 TLS session ticket —— 这些操作耗时且易被攻击者拖慢
- 用
sync.Map存储指纹计数,key 是ip:server_name字符串,value 是原子计数器
务必配合 context deadline,否则 Wait 会永久阻塞
rate.Limiter.Wait 默认不带超时,一旦限流器长期满载,goroutine 就卡死在 Wait 上,导致内存和 goroutine 泄漏。
正确写法是传一个带 deadline 的 context.Context:
- 在 handler 开头用
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) - 调用
limiter.Wait(ctx),超时后自动返回context.DeadlineExceeded - 必须调
cancel(),否则 context 泄漏;建议 defer cancel() - 不要复用同一个
context.Background(),它没有 deadline,也不响应取消信号
最易被忽略的是:限流逻辑必须在 http.Request 解析完成前执行,否则攻击者可通过构造畸形 header 触发解析 hang,绕过所有应用层防护。


















