Fiber 默认不重试 HTTP 请求,因其 ctx.SendString()、ctx.JSON() 及底层 fasthttp.Client 均为单次发送、失败即返错的设计,需显式封装(如用 tenacity)才支持重试。

为什么 Fiber 默认不重试 HTTP 请求
Fiber 的 ctx.SendString()、ctx.JSON() 或底层 fasthttp.Client 调用本身不带重试逻辑——它只做一次请求,失败就直接返回错误(如 fasthttp.ErrTimeout、fasthttp.ErrConnectionClosed)。这和 requests 一样,是“发完即止”的设计。你不显式封装,就不会有重试。
用 tenacity 封装外部 API 调用(推荐)
在 Fiber 路由中调用第三方服务(比如支付网关、短信平台)时,最稳妥的方式是把请求逻辑抽成独立函数,再用 tenacity 装饰。这样既保持路由轻量,又复用重试策略。
-
stop_after_attempt(3)是底线:少于 3 次容易被瞬时抖动击穿;多于 5 次可能拖垮你的 Fiber worker goroutine -
wait_exponential(multiplier=1, min=500, max=3000):首次失败等 500ms,第二次 1s,第三次 2s(不是线性叠加),避免雪崩式重试 - 只对可恢复异常重试:
retry_if_exception_type((fasthttp.ErrTimeout, fasthttp.ErrConnectionReset)),跳过fasthttp.StatusBadRequest这类 4xx 错误 - 别在
@retry函数里直接用ctx.Status()或ctx.JSON()——重试过程不应影响响应生命周期,只负责拿数据
示例:
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import fasthttp <p>@retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=500, max=3000), retry=retry_if_exception_type((fasthttp.ErrTimeout, fasthttp.ErrConnectionReset)) ) def call_payment_service(url: str) -> dict: resp, err = fasthttp.Get(url) if err != nil: raise err if resp.StatusCode() >= 400:</p><h1>注意:status code 错误需手动 raise 才能触发重试</h1><pre class="brush:php;toolbar:false;"> raise fasthttp.StatusError(resp.StatusCode()) return json.loads(resp.Body())
用 fasthttp.Client 自定义 Transport 级重试(进阶)
如果你用的是 fasthttp.Client 实例(比如全局复用的 client),可以改写它的 DoDeadline 方法或包装 Do,但要注意:fasthttp 不提供内置重试钩子,必须自己控制循环和错误分类。
- 不能无差别重试所有错误——比如
fasthttp.StatusTooManyRequests(429)要配合Retry-Afterheader 解析,而不是固定指数退避 - 每次重试前必须
resp.Body()读完并resp.Close(),否则连接不会归还到池里,导致too many open files - 设置
client.MaxIdleConnDuration和client.ReadTimeout要比重试总耗时小,否则单次请求就超时,重试没机会执行
回退(fallback)不是重试的替代品
重试失败后,你得明确告诉用户“暂时不可用”,而不是静默返回空或默认值。Fiber 中最直接的做法是在重试装饰器外层加 try/except,捕获 tenacity.RetryError 后走降级逻辑:
- 返回缓存数据(需提前预热,且标注
X-Cache: HIT) - 调用备用接口(比如主短信通道挂了切到阿里云通道)
- 记录告警并返回
ctx.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "upstream_unavailable"})
关键点:回退路径必须和主路径隔离,不能共享同一份 fasthttp.Client 实例或连接池,否则主服务故障可能连带拖垮备用链路。











