iris 框架本身不提供 http 客户端请求重试机制,需自行基于 net/http 和 backoff/v4 实现;应避免使用 http.defaultclient,为每次转发创建带超时的独立 context,并按状态码区分重试策略。

请求重试不是 Iris 内置功能
Iris 框架本身不提供 HTTP 客户端请求重试机制——iris.Context 是服务端上下文,只处理入站请求;出站调用(比如网关转发到后端服务)需你自行构造 HTTP 客户端并实现重试逻辑。很多人误以为 app.Retry() 或中间件里有现成 retry 配置,实际并不存在。
用 net/http + backoff 实现可配置的重试转发
API 网关场景下,典型做法是:收到请求 → 构造新 *http.Request → 用带重试能力的 *http.Client 转发 → 返回响应。推荐组合 net/http 和 github.com/cenkalti/backoff/v4:
- 不要直接用
http.DefaultClient,它无超时、无重试、易阻塞 - 为每次转发创建独立
context.WithTimeout,避免上游请求取消影响重试周期 - 重试判定需区分:连接失败(可重试)、5xx(通常可重试)、4xx(一般不重试,如
400 Bad Request或401 Unauthorized) - 指数退避比固定间隔更友好,
backoff.ExponentialBackOff可控最大尝试次数和上限时间
示例片段(简化版):
import (
"net/http"
"time"
"github.com/cenkalti/backoff/v4"
)
func doWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
var resp *http.Response
err := backoff.Retry(func() error {
var e error
resp, e = client.Do(req)
if e != nil {
return e // 连接失败,重试
}
if resp.StatusCode >= 500 && resp.StatusCode
<h3>在 Iris 中间件或 Handler 里集成重试转发</h3>
<p>关键点在于:别在 <code>ctx.Next()</code> 后才发起重试,而应在路由匹配后、响应写入前完成整个转发+重试流程。常见错误是把重试逻辑写在 <code>ctx.OnResponse</code> 里,此时响应头已发送,无法再改状态码或 body。</p>
- 转发前用
ctx.Request().URL.Path和ctx.Method()构造目标地址,注意保留原始 query 和 header(除Connection、Transfer-Encoding等 hop-by-hop 字段) - 用
ctx.Request().Body读取一次后,需用io.NopCloser(bytes.NewReader(bodyBytes))重建可复用 Body —— 原始 Body 是单次读取流,重试时会 EOF - 若后端返回非 2xx,建议统一转成
ctx.StatusCode(502)并写入简明错误体,避免暴露后端细节 - 别在重试中修改原始
ctx的值(如ctx.Values().Set()),除非明确需要跨中间件传递状态
重试容易被忽略的副作用
重试看似简单,但在生产网关中极易引发连锁问题:
- 上游客户端设置短超时(如 3s),而你重试 3 次 × 2s = 6s,结果上游早已断连,下游却还在发第 2 次请求
- POST/PUT 请求重试可能造成重复提交(如扣款),必须依赖后端幂等性设计,Iris 层无法自动保证
- 日志里出现大量相同 traceID 的多次出站请求,但没打标 “retry=1/2/3”,排查时无法区分首次失败还是重试失败
- 所有重试共用一个
http.Client的 Transport,若 MaxIdleConnsPerHost 不够,高并发下会卡在连接池排队,看起来像“重试卡住”
真正要稳,得在重试前先判断 method 是否安全(GET/HEAD 可无条件重试,POST 必须加幂等 key 校验),且每次重试日志显式标注 attempt index 和 sleep duration。











