Iris 的 iris.HTTPErrorHandler 不能实现熔断,因其仅处理已进入路由链的失败请求;熔断必须在请求抵达 handler 前通过中间件介入,并配合 sony/gobreaker 等第三方库按接口粒度封装,配置 MaxRequests、Timeout 和 ReadyToTrip 等参数。

iris.HTTPErrorHandler 不能直接实现熔断
很多人以为在 iris.HTTPErrorHandler 里加个计数器就能做熔断,其实不行。这个函数只处理已进入路由链但出错的请求,而熔断必须在请求真正打到 handler 前就拦截——否则失败请求照样消耗 goroutine、DB 连接、Redis 连接池,系统早被拖垮了。
真正该介入的位置是中间件(iris.Middleware),且必须配合外部状态存储(比如 Redis)或进程内原子计数器 + 时间窗口滑动,否则多实例部署时熔断状态不同步,等于没做。
用 circuitbreaker 包手动包装 handler 是最可控方式
iris 本身不内置熔断器,得靠第三方包,推荐 sony/gobreaker:它轻量、无依赖、状态可序列化,适合嵌入到单个接口级别。
- 不要全局 wrap 整个 app,而是按需 wrap 高风险 endpoint,比如调外部支付或 Redis Lua 扣库存的接口
- 配置时重点调这三项:
MaxRequests(窗口内最大允许请求数)、Timeout(断开后多久尝试半开)、ReadyToTrip(失败率阈值函数) - 别用默认的
DefaultSettings:它的MaxRequests=1太激进,高并发下容易误熔;建议设为50–100,配合Timeout: 30 * time.Second
示例片段:
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "pay-service",
MaxRequests: 80,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
return float64(counts.TotalFailures)/float64(counts.Requests) > 0.3
},
})
<p>app.Post("/pay", func(ctx iris.Context) {
err := cb.Execute(func() (interface{}, error) {
// 这里放真实调用外部服务的逻辑
return callThirdPartyPayAPI(ctx)
})
if err != nil {
if errors.Is(err, gobreaker.ErrOpenState) {
ctx.StatusCode(503)
ctx.WriteString("service unavailable")
return
}
ctx.StatusCode(500)
ctx.WriteString("internal error")
return
}
ctx.StatusCode(200)
ctx.JSON(map[string]string{"status": "ok"})
})
超时设置必须分层:read/write/ctx 要各自独立控制
只设 ReadTimeout 和 WriteTimeout 不够。Iris 的 Context 自带 ctx.Timeout(),但它和 HTTP Server 层的超时是两套机制,混用会互相覆盖或失效。
- HTTP Server 层(影响连接建立、TLS 握手、header 解析):
su.Server.ReadTimeout/WriteTimeout,建议统一设为15s,避免慢连接占满连接队列 - 业务逻辑层(防 handler 卡死):用
ctx.Timeout(time.Second * 8)包裹耗时操作,比如redis.Client.Do()或xorm.Session.Find() - 别漏掉中间件里的 timeout:比如日志中间件如果用了
ctx.Values().GetString读 body,要确保前面已用ctx.ReadBody()并设好 timeout,否则可能卡在 read body 阶段不触发 server 层超时
生产环境必须关掉 debug 熔断开关
开发时常用 GODEBUG=gctrace=1 或 iris.WithoutVersionChecker 来快速验证,但这些会严重干扰熔断判断。GC 日志 IO 拖慢响应,导致本可成功的请求超时;版本检查关闭后,某些中间件行为异常,失败率虚高。
上线前务必确认:
-
GODEBUG环境变量未启用任何 trace 类参数 - 所有熔断器的
Timeout值大于实际 P95 延迟至少 2 倍(比如压测看到 P95 是 1.2s,Timeout至少设 3s) - Redis 熔断状态存储使用单独连接池,避免和业务 Redis 共用连接,否则熔断器自己先挂了
最常被忽略的一点:熔断器的 ReadyToTrip 函数里,counts.Requests 是窗口内总请求数,不是并发数。如果你每秒打 1000 QPS,窗口设 10 秒,那 Requests 最大能到 10000,失败率计算必须用浮点除法,整数除会导致永远不熔断。


















