inboundqps配bbr防刷效果差,因其依赖cpu/load指标,而刷量请求(如get /health)资源消耗极低,无法触发阈值调整,导致攻击下db连接池耗尽却无告警;concurrency限流更有效,直接限制并发执行数,适用于慢接口防堆积。

为什么直接用 InboundQPS + BBR 策略防刷效果差
InboundQPS 配 BBR 看似能动态调阈值,但实际对「接口刷量」类攻击基本无效。因为 BBR 是基于系统 CPU/Load 做自适应,而刷接口的请求往往极轻(比如 GET /health),CPU 占用几乎为 0,Load 也压不上去,结果就是阈值始终卡在配置的 200 QPS,根本起不到防护作用。
常见错误现象:
- 攻击者用几十台机器每秒发 500 个空请求,服务响应正常、监控无告警,但 DB 连接池被打满
- 日志里全是
200 OK,但慢查询陡增,用户真实请求开始超时
真正有效的防刷,得从请求源头做识别和压制,而不是等系统扛不住才反应。所以必须换策略。
Concurrency 限流对慢接口防堆积更直接
如果你的被刷接口本身耗时长(比如导出 Excel、查聚合报表、调第三方 HTTP),Concurrency 是比 QPS 更精准的防御手段。它限制的是「同时正在执行的请求数」,不是「每秒进来多少」。
使用场景:
- 接口平均响应时间 > 500ms
- 后端依赖数据库连接池、HTTP 客户端连接数有限
- 出现大量
context deadline exceeded或sql: connection pool exhausted
配置示例:
{ MetricType: system.Concurrency, TriggerCount: 10, Strategy: system.Reject, }
这表示最多允许 10 个请求并发执行,第 11 个直接拒绝,不排队、不等待。比让请求卡在队列里耗尽 goroutine 更安全。
注意点:
-
Concurrency不统计已完成请求,只看ctx是否还在活跃中 - 必须确保所有 handler 都正确使用
context.WithTimeout,否则 goroutine 泄漏会导致计数不准 - 和
InboundQPS不能共存于同一system.LoadRules调用里,Sentinel 会报错
单机 IP 级令牌桶才是防刷主力
防刷的核心是「识别并隔离恶意来源」,最简单有效的方式就是按客户端 IP 做令牌桶限流。Gin 自己不带分布式能力,所以优先用内存版(sync.Mutex + 时间戳滑动窗口)起步,够用且无依赖。
关键参数选择:
-
rate:建议设为 1~5 QPS,防脚本暴力遍历,又不影响正常用户手动操作 -
capacity:设为rate × 2,允许短时突发(比如双击刷新) - 拦截响应码必须用
429 Too Many Requests,别用 200 包装错误,否则爬虫不会退避
示例中间件片段:
func IPRateLimitMiddleware(rate, capacity int) gin.HandlerFunc {
buckets := sync.Map{}
return func(c *gin.Context) {
ip := c.ClientIP()
if bucket, _ := buckets.LoadOrStore(ip, newTokenBucket(float64(rate), float64(capacity))); true {
if !bucket.(*tokenbucket).allow() {
c.Header("Retry-After", "1")
c.AbortWithStatusJSON(429, gin.H{"msg": "Too many requests from this IP"})
return
}
}
c.Next()
}
}
容易踩的坑:
-
c.ClientIP()在反向代理后可能拿到的是 Nginx 的内网 IP,需配engine.ForwardedByClientIP = true并信任代理头 - 不清理过期 IP 桶,
sync.Map会长期累积,建议加定时清理 goroutine(比如每 5 分钟扫一次 lastAccess
Redis 分布式令牌桶上线前必验三件事
多实例部署时,单机限流失效,必须上 Redis。但直接套用go.uber.org/ratelimit 或手写 Lua 脚本容易翻车。
上线前确认:
- Redis 连接是否复用(别每次限流都新建 client)
- Lua 脚本是否原子性地完成「读当前计数 → 判断是否超限 → 写新计数 + 设置过期」三步,缺一不可
- 是否对
INCR返回值做了int64类型断言,Redis 返回的可能是nil或float64,Go redis client v9+ 默认返回 interface{}
一个最小可用 Redis 令牌桶 key 设计:rate:ip:{hash(ip)},TTL 设为窗口时间(如 60 秒),避免 key 污染。
复杂点在于——防刷不是只靠限流。IP 限流只是第一道门,后面还得接 User-Agent 黑名单、Referer 校验、JS Challenge(比如简单算术题),否则绕过成本太低。限流规则越严,越要留好运维出口(比如白名单 IP 绕过),不然半夜报警你没法快速处置。











