ThinkPHP HTTP客户端重试需用think\Http::create()创建实例并设setOption('retry',3),门面类think\facade\Http不支持;retry_delay为初始延迟,后续按2的幂次增长;仅网络异常和502/503/504等触发重试,业务500需手动处理;timeout须预留足够重试窗口,否则重试失效。

ThinkPHP HTTP客户端重试没生效?检查是否用了think\facade\Http而非think\Http
ThinkPHP 6.1+ 默认的 think\facade\Http 是门面类,它不直接支持重试配置;真正支持重试的是底层的 think\Http 实例(即 Http::create() 返回的对象)。很多同学在 Http::get() 里加 ['retry' => 3] 发现完全不重试,就是卡在这儿。
实操建议:
- 必须显式创建可配置的客户端:
$client = \think\Http::create(); - 重试策略要挂载到客户端实例,不是单次请求参数:
$client->setOption('retry', 3)->setOption('retry_delay', 1000); - 注意:门面
Http::get()走的是无状态快捷调用,所有配置都会被忽略
指数退避怎么配?retry_delay 不是固定等待,而是初始值
retry_delay 设置的是第一次失败后等待的毫秒数,后续每次重试会按 2 的幂次增长(即 1×, 2×, 4×, 8×…),这是 Guzzle 底层默认行为,ThinkPHP 封装层直接透传。如果你设了 retry_delay => 500 且 retry => 3,实际等待序列是:500ms → 1000ms → 2000ms。
常见错误现象:
立即学习“PHP免费学习笔记(深入)”;
- 以为
retry_delay => 1000就是每次等 1s,结果第三次重试隔了 4s 才发,怀疑超时逻辑异常 - 没意识到总耗时可能远超预期:3 次重试 + 指数退避 + 网络往返,轻松突破 10s
- 在高并发场景下,大量请求同步进入指数退避,可能引发下游雪崩(尤其没加 jitter)
哪些错误会触发重试?默认只重试网络层失败,业务 500 不算
ThinkPHP 基于 Guzzle,默认只对连接异常(如 cURL error 7、Connection refused)、超时(connect_timeout 或 timeout 触发)、以及 HTTP 状态码 5xx 中的部分(Guzzle 默认重试 502/503/504)生效。普通接口返回 {"code":500,"msg":"xxx"} 这种业务错误,不会触发重试——它根本没进重试判断逻辑,响应已成功接收。
如果需要业务错误也重试,得手动干预:
- 关闭自动重试:
$client->setOption('retry', 0) - 自己捕获响应,解析
$response->json(),判断code字段,再决定是否手动调用$client->post(...) - 别直接 throw Exception 后指望重试——那只是中断当前流程,不是重试
并发请求下重试导致时间翻倍?小心timeout和connect_timeout叠加
比如你设了 timeout => 3000(总超时 3s),又配了 retry => 2,但没调大 timeout,那么第一次请求耗时 2.8s 成功,没问题;但如果第一次 2.9s 失败,重试时只剩 100ms 可用,必然立刻超时,重试失效。更糟的是,connect_timeout(建连超时)独立计算,它也会吃掉重试窗口。
实操建议:
- 重试场景下,
timeout至少设为:(单次预期最大耗时 + retry_delay × (2^retry - 1)) × 1.5 - 显式设
connect_timeout,避免 DNS 或 TCP 握手卡住整个重试周期 - 生产环境务必监控
http_client_retry_count和http_client_total_time类指标,否则重试成了黑盒延迟放大器
重试本身不难,难的是让每次重试都落在合理的时间窗和错误范围内;漏掉任意一个边界条件,就从容错变成添堵。



















