iris.context中不可直接存rate.limiter,因其复用特性会导致限流维度错乱;应按key(如ip、user_id)从sync.map或内存缓存中获取独立limiter实例,burst建议设为rps×1~3且需结合下游水位压测调优。

iris.Context中直接用rate.Limiter要小心并发安全
iris 的 Context 是复用的,每次请求拿到的可能是同一个对象。如果你把 rate.Limiter 存在 ctx.Values() 或中间件闭包里,多个 goroutine 并发调用时会触发 rate.Limiter 内部的原子操作竞争——它本身是线程安全的,但误用存储位置会导致限流维度错乱(比如本该按用户限,却共享了同一个 limiter)。
正确做法是:每个限流策略对应一个独立的 rate.Limiter 实例,按 key(如 user_id、ip、api_path)从 map 或 sync.Pool 中取;不要把 limiter 当作 request-scoped 变量塞进 ctx。
- 别在中间件里写
limiter := rate.NewLimiter(10, 5)然后直接ctx.Set("limiter", limiter) - 要用
sync.Map或带 TTL 的内存缓存(如 freecache)按 key 管理 limiter 实例 - 若限流粒度是 IP,注意 X-Forwarded-For 头可能被伪造,需结合 realIP 配置校验
自定义 iris.Handler 实现 TokenBucket 限流中间件
iris 不内置限流中间件,但你可以用标准 golang.org/x/time/rate 封装一个轻量 handler。关键不是“怎么加中间件”,而是“怎么让令牌桶状态不跨请求污染”。
示例中用 sync.Map 缓存每个 IP 对应的 limiter,避免高频创建销毁:
var ipLimiters = sync.Map{} // key: string(ip), value: *rate.Limiter
func RateLimitByIP(rps, burst int) iris.Handler {
return func(ctx iris.Context) {
ip := ctx.RemoteAddr() // 或用 ctx.GetHeader("X-Real-IP")
if limiter, ok := ipLimiters.Load(ip); ok {
if !limiter.(*rate.Limiter).Allow() {
ctx.StatusCode(429)
ctx.WriteString("too many requests")
return
}
ctx.Next()
return
}
// 首次访问,初始化 limiter
newLimiter := rate.NewLimiter(rate.Limit(rps), burst)
ipLimiters.Store(ip, newLimiter)
if !newLimiter.Allow() {
ctx.StatusCode(429)
ctx.WriteString("too many requests")
return
}
ctx.Next()
}
}
-
rate.Limit(rps)参数必须是rate.Limit类型,不能直接传int -
burst值建议 ≥rps,否则突发流量几乎必被拒(例如 rps=5、burst=1 时,第 2 个请求就失败) - sync.Map 在高并发下比 map+mutex 更高效,但要注意它不支持遍历清理过期项,长期运行需额外机制
分布式场景下别硬套 rate.Limiter,改用 Redis+Lua
单机 rate.Limiter 在多实例部署时完全失效——每个进程维护自己的桶,总流量会翻倍突破阈值。这时必须把令牌桶状态提到共享存储层。
基于官方 GMGN API 的代币分析工具。通过合约地址查询代币在 SOL/BSC/Base 链上的准确市场数据、安全检测、KOL 分析、开发者分析和 AI 智能分析(叙事/筹码/老鼠仓/机器人)。支持自动识别链。
iris 本身不绑定 Redis,但你可以用 github.com/go-redis/redis/v9 + Lua 脚本实现原子限流。核心是把三要素(tokens、last_refill_time、capacity)存在 Redis HASH,并用 Lua 保证“读-算-写”不被并发打断。
- Lua 脚本里必须用
redis.call("hget", KEYS[1], "tokens")而不是 pipeline,否则无法原子性 - 时间戳用
redis.call("time")获取,避免客户端时间不同步导致补令牌异常 - Redis key 命名建议含业务标识,如
"rate:api:/user/profile:192.168.1.100",防止 key 冲突
burst 设置不合理会导致限流形同虚设
很多人以为设了 rps=10 就能扛住每秒 10 请求,结果压测发现瞬间 50 QPS 也能过——问题大概率出在 burst 值过大。
例如 rate.NewLimiter(rate.Limit(10), 100):桶容量 100,意味着空闲 10 秒后桶就满了,此时突发 100 请求全放行。这已经不是“限流”,是“缓存请求”。
- burst 应该反映你系统真实能承受的瞬时峰值,通常取
rps × 1~3秒较合理 - 对写接口,burst 宜小(如 rps=5 → burst=5),避免数据库连接池被打满
- 对读接口且有缓存,burst 可稍大(如 rps=50 → burst=150),但需监控 Redis CPU 和延迟毛刺
真正难的不是写限流代码,是根据下游依赖的水位(DB 连接数、GPU 显存、第三方 API 配额)反推每个接口合理的 rps+burst 组合——这个没法靠文档,得靠压测和线上日志反复调。










