iris无内置限流中间件,需手动集成;常用方案包括标准库golang.org/x/time/rate(适合简单ip限流)或uber令牌桶、redis分布式限流,注意context不兼容及高并发下sync.map性能瓶颈。

Iris 没有内置的限流中间件,必须手动集成或自建逻辑,直接调用 app.Use 加个第三方限流器或自己用 sync.Map + 时间窗口计数是最快落地的方式。
为什么 Iris 不像 Gin 那样有现成的 gin-contrib/limiter
Iris 的生态中至今没有被官方推荐、持续维护的通用限流中间件。社区常见做法是:要么用 golang.org/x/time/rate 自行封装,要么引入轻量第三方如 uber-go/ratelimit 或 go.uber.org/ratelimit(注意不是 uber-go/ratelimit 的旧版别名)。golang.org/x/time/rate.Limiter 更适合请求级限流(比如每秒最多 10 次),而 go.uber.org/ratelimit 是“令牌桶”实现,对突发流量更友好。
常见错误是直接把 Gin 的限流中间件代码改函数名后塞进 Iris —— 会因 Context 类型不兼容(gin.Context vs iris.Context)编译失败,且 Iris 的路由分组和上下文生命周期管理也不同。
用 golang.org/x/time/rate 实现 IP 级简单限流
适用于开发环境验证或低并发场景,不依赖额外包,标准库即可搞定:
import (
"net"
"net/http"
"sync"
"time"
"golang.org/x/time/rate"
"github.com/kataras/iris/v12"
)
var (
ipLimiter = sync.Map{} // key: string(ip), value: *rate.Limiter
limiterRPS = 5 // 每秒最多 5 次
limiterBurst = 10 // 允许突发 10 次
)
func rateLimitMiddleware() iris.Handler {
return func(ctx iris.Context) {
ip, _, _ := net.SplitHostPort(ctx.Request().RemoteAddr)
if ip == "" {
ip = ctx.Request().RemoteAddr
}
limiter, ok := ipLimiter.Load(ip)
if !ok {
newLimiter := rate.NewLimiter(rate.Limit(limiterRPS), limiterBurst)
limiter, _ = ipLimiter.LoadOrStore(ip, newLimiter)
}
if !limiter.(*rate.Limiter).Allow() {
ctx.StatusCode(http.StatusTooManyRequests)
ctx.WriteString("Too many requests")
return
}
ctx.Next()
}
}
// 使用
app.Use(rateLimitMiddleware())
注意点:
-
sync.Map不适合高并发下频繁写入(比如每秒数万请求),此时应换用带 TTL 的内存缓存如go-cache或 Redis - 该实现未处理 IPv6 地址归一化,真实部署需用
net.ParseIP+ip.To4()统一降级为 IPv4 再哈希 - 未区分路由路径,所有请求共用一个桶;若需 per-route 限流,key 应改为
ip + ":" + ctx.Path()
用 Redis 实现分布式限流(生产推荐)
单机限流在多实例部署时完全失效,必须上 Redis。Iris 本身不绑定任何 Redis 客户端,推荐用 github.com/go-redis/redis/v8:
核心逻辑是用 INCR + EXPIRE 组合实现滑动窗口,避免 Lua 脚本复杂度:
func redisRateLimit(client *redis.Client, key string, limit int, window time.Duration) bool {
pipe := client.Pipeline()
inc := pipe.Incr(context.Background(), key)
pipe.Expire(context.Background(), key, window)
_, err := pipe.Exec(context.Background())
if err != nil {
return true // 出错时放行,避免雪崩
}
count, _ := inc.Result()
return int(count)
<p>使用时注意:</p>
- key 必须唯一可预测,例如
"rl:" + clientIP + ":" + path,避免 key 泛滥 - 不要在中间件里阻塞等待 Redis 响应,超时设为
100ms以内,失败默认放行 - Redis 连接池大小建议 ≥ 实例数 × 20,否则限流本身成瓶颈
真正难的不是写几行限流代码,而是决定「按什么维度限」和「限多少」——IP?Token?User ID?还是 API Key?这些策略一旦上线就很难动态调整,建议初期就把限流规则外置到配置中心或数据库,别硬编码。











