PHP cURL重试需结合指数退避(上限8秒)、仅重试可恢复错误(如5xx、429、连接超时等),并严格校验HTTP状态码与cURL错误码,同时保留Idempotency-Key和User-Agent等关键头信息。

用 curl_setopt 控制重试次数和退避间隔
PHP 原生 curl 不自带重试逻辑,必须手动封装。关键不是“重试多少次”,而是每次失败后是否等待、等多久——这直接影响下游服务压力和自身请求成功率。
常见错误是直接套用固定 sleep(1),结果在 502 错误时连续猛冲,反而触发对方限流;或者在 DNS 解析失败时还傻等 2 秒,浪费超时时间。
- 用
CURLOPT_CONNECTTIMEOUT_MS和CURLOPT_TIMEOUT_MS显式设为毫秒级,避免默认 0(无限)或过长(300 秒)导致重试卡死 - 退避建议用指数退避:第 1 次失败后
sleep(1),第 2 次sleep(2),第 3 次sleep(4),上限建议不超过 8 秒 - 只对可重试错误重试:
CURLE_COULDNT_CONNECT、CURLE_OPERATION_TIMEDOUT、CURLE_HTTP_RETURNED_ERROR(且响应码是 5xx) - 跳过不可重试错误:如
CURLE_URL_MALFORMED、CURLE_SSL_CACERT,这类问题重试无意义
用 http_response_code() 和 curl_getinfo($ch, CURLINFO_HTTP_CODE) 区分失败类型
很多同学只看 curl_exec() 返回 false 就重试,结果把 400 Bad Request 或 401 Unauthorized 当成网络抖动反复发,既暴露业务逻辑,又可能被风控拦截。
真实场景中,HTTP 状态码比 cURL 错误码更能说明问题:500/502/503/504 是典型可重试信号;429 虽属 4xx,但表示限流,也应纳入退避重试范围;而 400/404/405 基本意味着请求本身有缺陷,重试只是徒劳。
立即学习“PHP免费学习笔记(深入)”;
- 务必在
curl_exec()后立刻调用curl_getinfo($ch, CURLINFO_HTTP_CODE)获取状态码 - 不要依赖
http_response_code(),它返回的是当前脚本的响应码,不是远程 API 的 - 对 429 响应,优先读取
Retry-AfterHeader,而不是硬套指数退避
用 try/catch 包裹 file_get_contents + stream_context_create 实现轻量重试
如果项目禁用 curl(如某些共享主机),或只想快速补个重试逻辑,file_get_contents 配合自定义 context 是更轻的选择,但要注意它默认不抛异常,必须显式开启。
性能上,file_get_contents 在小数据、低并发下开销略小;但无法细粒度控制连接超时与读取超时分离,也不支持 HTTP/2 或复杂 Cookie 管理,高要求场景仍推荐 cURL。
- 设置
http.context时,必须加'ignore_errors' => true,否则 4xx/5xx 会直接报 Warning 而不进 catch - 用
stream_context_set_default()全局设 timeout 容易污染其他 stream 操作,建议每次调用都新建 context - 重试循环里记得
clearstatcache()?不用——这是文件系统缓存,和 HTTP 无关
重试时别漏掉 User-Agent 和 Idempotency-Key 头
有些 API(尤其是支付、订单类)要求幂等性。没带 Idempotency-Key 的重试请求,可能被当成新操作执行两次;而缺失 User-Agent 则容易被 WAF 拦截,尤其在重试密集时。
这不是“最好加上”,而是“不加就可能失败”。比如 Stripe、Shopify、腾讯云 API 文档都明确要求重试请求携带原始 Idempotency-Key,否则返回 409 Conflict。
-
Idempotency-Key应在首次请求前生成(UUID v4 最稳妥),并原样复用于所有重试请求 -
User-Agent建议包含应用名+版本+PHP 版本,例如"MyApp/2.1 PHP/8.2",方便对方排查 - 不要在重试时修改
Content-Length或Content-MD5,除非 payload 真的变了——但重试不该改 payload



















