PHP请求重试和超时设置

wen PHP项目 3

本文目录导读:

PHP请求重试和超时设置

  1. cURL 请求
  2. Guzzle HTTP Client(推荐)
  3. 文件流获取(大文件下载重试)
  4. 异步请求(ReactPHP/Swoole)
  5. 通用重试函数
  6. 超时设置的注意事项
  7. 完整示例:带重试的API请求类
  8. 最佳实践建议

在PHP中进行HTTP请求时,设置超时和重试机制非常重要,以下是各种场景下的详细配置方法:

cURL 请求

基础配置

function curlRequest($url, $options = []) {
    $defaults = [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,      // 连接超时(秒)
        CURLOPT_TIMEOUT => 30,             // 总超时(秒)
        CURLOPT_FOLLOWLOCATION => true,    // 跟随重定向
        CURLOPT_MAXREDIRS => 5,           // 最大重定向次数
    ];
    $ch = curl_init();
    curl_setopt_array($ch, array_replace($defaults, $options));
    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new Exception("Curl error: $error");
    }
    curl_close($ch);
    return $result;
}

重试机制实现

function requestWithRetry($url, $maxRetries = 3, $options = []) {
    $retry = 0;
    $delay = 1; // 初始延迟(秒)
    while ($retry < $maxRetries) {
        try {
            $response = curlRequest($url, $options);
            return $response;
        } catch (Exception $e) {
            $retry++;
            // 达到最大重试次数
            if ($retry >= $maxRetries) {
                throw new Exception("Request failed after $maxRetries attempts: " . $e->getMessage());
            }
            // 指数退避策略
            $sleepTime = $delay * pow(2, $retry - 1);
            echo "Retry $retry in {$sleepTime} seconds...\n";
            sleep($sleepTime);
        }
    }
}
// 使用示例
$url = "https://api.example.com/data";
try {
    $data = requestWithRetry($url, 3, [
        CURLOPT_TIMEOUT => 10,
        CURLOPT_CONNECTTIMEOUT => 3,
    ]);
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

Guzzle HTTP Client(推荐)

安装

composer require guzzlehttp/guzzle

基础配置和重试

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RetryMiddleware;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
// 创建客户端
$client = new Client([
    'base_uri' => 'https://api.example.com',
    'timeout' => 10,           // 总超时
    'connect_timeout' => 5,    // 连接超时
    'verify' => false,         // 开发环境可关闭SSL验证
    'http_errors' => true,     // 是否抛出HTTP错误异常
]);
// 简单请求
try {
    $response = $client->request('GET', '/data', [
        'timeout' => 30,        // 覆盖默认超时
    ]);
    $body = $response->getBody()->getContents();
} catch (RequestException $e) {
    echo "Request failed: " . $e->getMessage();
    if ($e->hasResponse()) {
        $statusCode = $e->getResponse()->getStatusCode();
        // 根据状态码决定是否重试
        if (in_array($statusCode, [408, 429, 500, 502, 503, 504])) {
            // 可以在这里重试
        }
    }
}

Guzzle 重试中间件

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
function retryMiddleware($maxRetries = 3, $delay = 1) {
    return function($handler) use ($maxRetries, $delay) {
        return function($request, $options) use ($handler, $maxRetries, $delay) {
            $attempt = 0;
            do {
                try {
                    $response = $handler($request, $options);
                    // 检查状态码
                    $statusCode = $response->getStatusCode();
                    if ($statusCode >= 500 || $statusCode == 408 || $statusCode == 429) {
                        if ($attempt >= $maxRetries - 1) {
                            return $response;
                        }
                        $attempt++;
                        sleep($delay * $attempt);
                        continue;
                    }
                    return $response;
                } catch (RequestException $e) {
                    if ($attempt >= $maxRetries - 1) {
                        throw $e;
                    }
                    $attempt++;
                    sleep($delay * $attempt);
                }
            } while ($attempt < $maxRetries);
        };
    };
}
// 使用重试中间件
$handler = HandlerStack::create();
$retryConfig = [
    'max' => 3,                    // 最大重试次数
    'delay' => 1000,               // 基础延迟(毫秒)
    'retry_if' => function($retries, $response, $request) {
        // 自定义重试条件
        if ($retries >= 3) {
            return false;
        }
        if ($response) {
            return in_array($response->getStatusCode(), [429, 500, 503]);
        }
        return true; // 网络错误时重试
    }
];
$handler->push(Middleware::retry($retryConfig['retry_if'], $retryConfig['delay']));
$client = new Client([
    'handler' => $handler,
    'timeout' => 30,
]);

文件流获取(大文件下载重试)

function downloadFileWithRetry($url, $destination, $maxRetries = 3) {
    $retry = 0;
    while ($retry < $maxRetries) {
        try {
            $ch = curl_init($url);
            $fp = fopen($destination, 'wb');
            curl_setopt($ch, CURLOPT_FILE, $fp);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 3600);      // 较长超时
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // 连接超时
            if (curl_exec($ch) === false) {
                throw new Exception("Download failed: " . curl_error($ch));
            }
            curl_close($ch);
            fclose($fp);
            // 验证文件大小
            if (filesize($destination) > 0) {
                return true;
            }
            throw new Exception("Downloaded file is empty");
        } catch (Exception $e) {
            $retry++;
            if ($retry >= $maxRetries) {
                throw new Exception("File download failed after $maxRetries attempts");
            }
            // 清理部分下载的文件
            if (file_exists($destination)) {
                unlink($destination);
            }
            echo "Retry downloading... Attempt $retry\n";
            sleep(2 * $retry);
        }
    }
}

异步请求(ReactPHP/Swoole)

Swoole 协程版本

use Swoole\Coroutine\Http\Client;
function asyncRequestWithRetry($url, $maxRetries = 3) {
    $retry = 0;
    while ($retry < $maxRetries) {
        $client = new Client('api.example.com', 443, true);
        $client->set([
            'timeout' => 5,
            'connect_timeout' => 3,
        ]);
        $client->get('/path/to/resource');
        if ($client->statusCode === 200) {
            $result = $client->body;
            $client->close();
            return $result;
        }
        $retry++;
        $client->close();
        if ($retry < $maxRetries) {
            co::sleep(500 * $retry); // 毫秒
        }
    }
    return null;
}

通用重试函数

/**
 * 通用重试函数
 */
function retry($callback, $maxAttempts = 3, $delay = 1000, $backoff = true) {
    $attempts = 0;
    while ($attempts < $maxAttempts) {
        try {
            return $callback();
        } catch (Exception $e) {
            $attempts++;
            if ($attempts >= $maxAttempts) {
                throw $e;
            }
            $waitTime = $backoff ? $delay * pow(2, $attempts - 1) : $delay;
            echo "Attempt $attempts failed. Retrying in {$waitTime}ms...\n";
            usleep($waitTime * 1000); // 转换为微秒
        }
    }
}
// 使用方式
$result = retry(function() {
    return file_get_contents("https://api.example.com/data");
}, 5, 1000); // 最多重试5次,基础延迟1秒

超时设置的注意事项

合理设置超时

$config = [
    // 连接超时:建立TCP连接的最大时间
    'connect_timeout' => 5,     // 建议 2-10 秒
    // 总超时:整个请求的最大时间
    'timeout' => 30,            // 建议 10-60 秒
    // 读超时:接收数据的最大间隔
    'read_timeout' => 5,        // 建议 2-10 秒
    // 写超时:发送数据的最大间隔  
    'write_timeout' => 5,       // 建议 2-10 秒
];

完整示例:带重试的API请求类

class ApiClient {
    private $client;
    private $maxRetries = 3;
    private $delay = 1000; // 毫秒
    public function __construct() {
        $this->client = new Client([
            'timeout' => 30,
            'connect_timeout' => 5,
            'headers' => [
                'Accept' => 'application/json',
                'User-Agent' => 'API-Client/1.0'
            ]
        ]);
    }
    public function request($method, $endpoint, $data = null) {
        $attempts = 0;
        while ($attempts < $this->maxRetries) {
            try {
                $response = $this->client->request($method, $endpoint, [
                    'json' => $data
                ]);
                return json_decode($response->getBody(), true);
            } catch (RequestException $e) {
                $attempts++;
                if ($attempts >= $this->maxRetries) {
                    throw new Exception("API request failed: {$e->getMessage()}");
                }
                $waitTime = $this->delay * pow(2, $attempts - 1);
                echo "Retrying request to $endpoint in {$waitTime}ms...\n";
                usleep($waitTime * 1000);
            } catch (Exception $e) {
                // 非HTTP异常直接抛出
                throw $e;
            }
        }
    }
    // 设置重试参数
    public function setRetryPolicy($maxRetries, $delay = 1000) {
        $this->maxRetries = $maxRetries;
        $this->delay = $delay;
    }
}

最佳实践建议

  1. 超时设置:连接超时建议 5 秒,总超时建议 30 秒,根据业务需求调整
  2. 重试次数:一般建议 3-5 次,过多会增加服务器压力
  3. 退避策略:使用指数退避(如 1s, 2s, 4s)而不是固定延迟,避免雪崩效应
  4. 只重试安全请求:GET、HEAD 等幂等操作可以重试;POST、PUT 等要谨慎
  5. 记录日志:记录重试次数和原因,便于调试
  6. 并发控制:高并发时应考虑限流,避免重试造成服务过载

这些方法应该能覆盖大多数 PHP HTTP 请求的超时和重试需求。

抱歉,评论功能暂时关闭!