PHP重试需精准判断错误类型:URL错误不重试,连接失败、超时及502/503/504等可重试;Guzzle需正确注入中间件并处理null响应;须用usleep毫秒级指数退避;非幂等操作重试前必须确认执行状态。

PHP 原生不提供接口重试能力,必须手动封装或依赖第三方库;是否启用重试、重试几次、等多久、重试什么错误——这些决策直接决定服务稳定性,而不是“加个 retry 就完事”。
curl_exec() 返回 false 后该不该重试?
不能只看 curl_exec() 是否返回 false。它掩盖了真实失败原因:
-
CURLE_URL_MALFORMED(URL 格式错误):重试毫无意义,应立刻修正参数 -
CURLE_COULDNT_CONNECT(连不上服务器):典型可重试场景,大概率是网络抖动或服务瞬时不可达 -
CURLE_OPERATION_TIMEDOUT(超时):需结合CURLOPT_TIMEOUT_MS判断是否属于预期范围内的抖动 -
CURLE_HTTP_RETURNED_ERROR(HTTP 状态码非 2xx):必须立刻调用curl_getinfo($ch, CURLINFO_HTTP_CODE)拿到真实状态码,再决定——502/503/504可重试,400/401/404不该重试
漏掉 curl_getinfo() 这一步,90% 的重试逻辑都会把业务错误当成网络故障猛冲。
Guzzle 的 RetryMiddleware 为什么没生效?
常见失效原因不是配置写错,而是底层判定逻辑被绕过:
立即学习“PHP免费学习笔记(深入)”;
- 没显式检查
$reason->getResponse()是否为 null:Guzzle v7+ 中,连接失败时getResponse()返回null,直接调用会报错,必须先判空 - 默认
RetryMiddleware不重试 5xx:它的默认判定函数只处理连接异常和超时,502这类响应需手动加入判断 - 没把中间件注入
HandlerStack:仅定义Middleware::retry()不生效,必须通过$stack->push()注入才起作用 -
Retry-After头被忽略:遇到429响应时,应优先读取该 Header,而不是硬套指数退避
示例判定函数关键片段:
function ($reason) {
if ($reason->hasResponse()) {
$code = $reason->getResponse()->getStatusCode();
return in_array($code, [429, 502, 503, 504]);
}
return $reason->getException() instanceof \GuzzleHttp\Exception\ConnectException
|| $reason->getException() instanceof \GuzzleHttp\Exception\TimeoutException;
}重试间隔用 sleep() 还是 usleep()?
用 usleep(),且必须是毫秒级精度的指数退避:
-
sleep(1)是整秒级,对首次重试太长,容易拖慢整体响应;usleep(100000)(100ms)更合理 - 推荐策略:
usleep(pow(2, $attempt) * 100000),即第 1 次等 100ms,第 2 次 200ms,第 3 次 400ms,上限不超过 800ms - 别在循环外初始化
$ch = curl_init():每次重试必须重新curl_init(),否则curl_setopt()可能复用旧句柄状态,导致超时设置失效 - 务必设
CURLOPT_CONNECTTIMEOUT_MS和CURLOPT_TIMEOUT_MS,避免默认无限等待卡死整个重试流程
固定间隔或无延迟重试,在高并发下极易引发下游雪崩,这不是容错,是攻击。
file_get_contents 能不能凑合用?
可以,但限制极多,仅适合低频、轻量、不允许装扩展的环境:
- 必须设
'ignore_errors' => true,否则 4xx/5xx 直接触发 PHP Warning,进不了try/catch -
http[timeout]单位是秒,不支持小数(设3.5会被截断成3),无法精细控制 - 不区分连接超时和读取超时,
stream_context_create()只有一个总 timeout - 若
allow_url_fopen=Off,这条路直接走不通 - 无法保留
Idempotency-Key等关键请求头,幂等性难保障
它没有重试逻辑,所谓“重试”只是你用 for 循环包了一层——和手写 cURL 本质一样,只是少了错误码粒度控制。
最易被忽略的一点:重试本身有副作用。如果接口不是幂等的(比如扣款、发短信),重试前必须确认是否已执行成功,否则可能重复操作。这比怎么重试更重要。



















