Beego 本身不提供内置请求重试机制,重试逻辑应加在 Controller 方法内发起外部调用处(如 http.Client、ORM 或微服务调用),需按错误类型差异化处理、每次新建 backoff 实例、避免在 Prepare/Filter/Model 层封装。

Beego 本身不提供内置的请求重试机制——它只负责 HTTP 请求分发和 Controller 执行,下游调用(比如访问另一个服务、数据库、第三方 API)是否重试、怎么重试,完全由你控制。
Beego 中哪里该加重试逻辑
重试不是加在路由或 Filter 里,而是加在 Controller 方法内部真正发起外部调用的位置。比如你用 http.Client 调第三方接口、用 beego.ORM 执行查询、或通过 micro.Client 调微服务时,这些才是重试的“作用点”。
- 不要在
Prepare()或全局 Filter 中封装重试——它们不感知具体业务动作,容易误重试非幂等操作 - 避免在
Model层硬编码重试——会污染数据访问逻辑,且难以按错误类型差异化策略 - 推荐把重试封装成独立函数,例如
DoWithRetry(req *http.Request, backoff backoff.BackOff) (*http.Response, error),然后在 Controller 里显式调用
HTTP 请求重试必须隔离 BackOff 实例
并发场景下共用同一个 backoff.BackOff 实例会导致退避状态错乱:A 请求刚重试完调了 bo.NextBackOff(),B 请求紧接着拿到的是已被推进的状态,实际等待时间远小于预期。
- 每次发起请求前,必须新建实例:
bo := backoff.NewExponentialBackOff() - 不要从全局变量或 sync.Pool 复用
backoff.BackOff——它不是无状态配置,而是带计数器和时间戳的运行时对象 - 若需统一最大重试次数,用
backoff.WithMaxRetries(fn, 3)包裹,而不是靠共享实例的MaxElapsedTime控制
按错误类型动态决定是否重试
直接对所有 error 无差别重试是危险的。400 类错误(如 400 Bad Request)通常说明请求本身有缺陷,重试只会放大问题;而 503、timeout、connection refused 才是典型的可重试场景。
- 用
errors.As(err, &url.Error{})判断是否网络错误 - 对
*http.Response,检查resp.StatusCode >= 500 && resp.StatusCode - 对 Beego ORM,捕获
orm.ErrNoRows不该重试,但driver.ErrBadConn可以重试 - 别写
if err != nil { retry() }这种粗粒度判断
Beego Controller 中重试的典型结构
下面是一个真实可用的片段,注意上下文超时与重试的协同:
func (c *MainController) CallExternalAPI() {
ctx, cancel := context.WithTimeout(c.Ctx.Request.Context(), 10*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
resp, err := DoWithRetry(req, func() (bool, error) {
// 这里只做“是否值得再试一次”的判断
if errors.Is(err, context.DeadlineExceeded) ||
(resp != nil && resp.StatusCode >= 500 && resp.StatusCode < 600) {
return true, nil
}
return false, err
})
if err != nil {
c.Abort("502")
return
}
defer resp.Body.Close()
// ... 处理响应
}
最易被忽略的一点:重试逻辑里不能修改 Controller 的字段(如 c.Data 或 c.Ctx),否则多个并发请求会互相覆盖状态。所有中间状态必须局限在函数作用域内。


















