本文目录导读:

在PHP项目中处理网络请求超时是一个常见且重要的问题,下面我为你整理一份全面的超时处理方案。
常见网络请求方式的超时设置
cURL 请求超时处理
<?php
/**
* cURL请求超时处理
*/
function curlRequest($url, $options = []) {
$ch = curl_init();
// 默认超时设置
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5, // 连接超时时间(秒)
CURLOPT_TIMEOUT => 10, // 总超时时间(秒)
CURLOPT_SSL_VERIFYPEER => false, // 跳过SSL验证(生产环境建议开启)
CURLOPT_SSL_VERIFYHOST => false,
];
// 合并自定义选项
if (!empty($options)) {
$defaultOptions = $options + $defaultOptions;
}
curl_setopt_array($ch, $defaultOptions);
$response = curl_exec($ch);
// 检查错误
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL错误: {$error}");
}
// 获取HTTP状态码
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'code' => $httpCode,
'data' => $response
];
}
// 使用示例
try {
$result = curlRequest('https://api.example.com/data', [
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 8,
]);
echo "请求成功";
} catch (Exception $e) {
echo "请求失败: " . $e->getMessage();
}
file_get_contents 超时处理
<?php
/**
* file_get_contents 超时处理
*/
function fileGetContentsWithTimeout($url, $timeout = 5) {
$context = stream_context_create([
'http' => [
'timeout' => $timeout, // 超时时间(秒)
'method' => 'GET',
'header' => "User-Agent: PHP\r\n"
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
]
]);
$result = @file_get_contents($url, false, $context);
if ($result === false) {
// 检查错误
$error = error_get_last();
throw new Exception("file_get_contents错误: " . $error['message']);
}
return $result;
}
// 使用示例
try {
$data = fileGetContentsWithTimeout('https://api.example.com/data', 3);
echo "请求成功";
} catch (Exception $e) {
echo "请求失败: " . $e->getMessage();
}
Guzzle HTTP 客户端(推荐)
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TimeoutException;
/**
* Guzzle 请求超时处理
*/
class GuzzleClient {
private $client;
public function __construct() {
$this->client = new Client([
'timeout' => 10, // 请求超时时间(秒)
'connect_timeout' => 5, // 连接超时时间(秒)
'read_timeout' => 5, // 读取超时时间(秒)
'verify' => true, // SSL验证
]);
}
public function request($method, $url, $options = []) {
try {
$response = $this->client->request($method, $url, $options);
return $response->getBody()->getContents();
} catch (TimeoutException $e) {
// 请求超时
throw new Exception("请求超时: " . $e->getMessage());
} catch (ConnectException $e) {
// 连接失败
throw new Exception("连接失败: " . $e->getMessage());
} catch (RequestException $e) {
// 其他请求错误
throw new Exception("请求错误: " . $e->getMessage());
}
}
}
// 使用示例
$client = new GuzzleClient();
try {
$data = $client->request('GET', 'https://api.example.com/data', [
'timeout' => 3,
'connect_timeout' => 2,
]);
echo "请求成功";
} catch (Exception $e) {
echo "请求失败: " . $e->getMessage();
}
异步请求处理(适合长时间任务)
<?php
/**
* 使用 ReactPHP 进行异步请求
*/
use React\EventLoop\Factory;
use React\HttpClient\Client;
require 'vendor/autoload.php';
function asyncRequest($url, $callback) {
$loop = Factory::create();
$client = new Client($loop);
$request = $client->request('GET', $url);
$timeoutTimer = $loop->addTimer(10, function() use ($request) {
$request->close();
echo "请求超时!\n";
});
$request->on('response', function($response) use ($timeoutTimer, $callback) {
$response->on('data', function($chunk) use ($callback) {
echo "收到数据: " . $chunk;
$callback($chunk);
});
$response->on('end', function() use ($timeoutTimer) {
$loop->cancelTimer($timeoutTimer);
echo "请求完成\n";
});
});
$request->on('error', function(\Exception $e) use ($timeoutTimer) {
$loop->cancelTimer($timeoutTimer);
echo "请求错误: " . $e->getMessage() . "\n";
});
$request->end();
$loop->run();
}
// 使用示例
asyncRequest('https://api.example.com/data', function($data) {
// 处理数据
});
全局超时配置方案
自定义超时管理类
<?php
/**
* 统一的超时管理类
*/
class TimeoutManager {
private static $instance = null;
private $timeoutConfig = [];
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 设置超时配置
*/
public function configure(array $config) {
$this->timeoutConfig = array_merge($this->timeoutConfig, [
'default_timeout' => 10, // 默认线程超时(秒)
'connect_timeout' => 5, // 默认连接超时(秒)
'max_retries' => 2, // 最大重试次数
'retry_delay' => 3, // 重试延迟(秒)
], $config);
}
/**
* 带超时控制的请求
*/
public function requestWithTimeout($url, $callback, $options = []) {
$timeout = $options['timeout'] ?? $this->timeoutConfig['default_timeout'];
$connectTimeout = $options['connect_timeout'] ?? $this->timeoutConfig['connect_timeout'];
$attempts = 0;
$maxRetries = $options['max_retries'] ?? $this->timeoutConfig['max_retries'];
while ($attempts <= $maxRetries) {
try {
$startTime = microtime(true);
// 设置 PHP 执行时间限制(防止脚本超时)
set_time_limit($timeout + 10);
// 执行请求
$result = $callback([
'timeout' => $timeout,
'connect_timeout' => $connectTimeout
]);
$elapsedTime = microtime(true) - $startTime;
// 记录日志
$this->logRequest($url, $elapsedTime, true);
return $result;
} catch (\Exception $e) {
$attempts++;
// 记录错误日志
$this->logRequest($url, 0, false, $e->getMessage());
if ($attempts <= $maxRetries) {
sleep($this->timeoutConfig['retry_delay']);
} else {
throw $e;
}
}
}
throw new Exception("请求失败,重试次数已用完");
}
/**
* 日志记录
*/
private function logRequest($url, $elapsedTime, $success, $error = '') {
$logData = [
'timestamp' => date('Y-m-d H:i:s'),
'url' => $url,
'elapsed_time' => round($elapsedTime, 3),
'success' => $success,
'error' => $error
];
// 写入日志文件或日志系统
error_log(json_encode($logData) . PHP_EOL, 3, '/var/log/php_requests.log');
}
/**
* 压测时的超时控制
*/
public function benchmark($url, $callback, $concurrent = 5) {
$results = [];
$startTime = microtime(true);
// 并发请求(使用多进程或异步方式)
$pids = [];
for ($i = 0; $i < $concurrent; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
throw new Exception("无法创建子进程");
} elseif ($pid) {
$pids[] = $pid;
} else {
// 子进程执行
try {
$result = $callback();
exit(0);
} catch (Exception $e) {
exit(1);
}
}
}
// 等待所有子进程完成
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
}
$elapsedTime = microtime(true) - $startTime;
return [
'total_time' => $elapsedTime,
'concurrent' => $concurrent,
'success' => array_sum($results)
];
}
}
// 使用示例
$manager = TimeoutManager::getInstance();
$manager->configure([
'default_timeout' => 8,
'connect_timeout' => 3,
'max_retries' => 3,
]);
try {
$result = $manager->requestWithTimeout(
'https://api.example.com/data',
function($timeout) {
// 使用 cURL 或其他方式请求
return curlRequest('https://api.example.com/data', [
CURLOPT_TIMEOUT => $timeout['timeout'],
CURLOPT_CONNECTTIMEOUT => $timeout['connect_timeout'],
]);
}
);
var_dump($result);
} catch (Exception $e) {
echo "请求最终失败: " . $e->getMessage();
}
Laravel 中的超时处理
<?php
// Laravel 中使用 HTTP 客户端
use Illuminate\Support\Facades\Http;
try {
$response = Http::timeout(10) // 总超时
->connectTimeout(5) // 连接超时
->retry(3, 100) // 重试3次,间隔100ms
->get('https://api.example.com/data');
if ($response->successful()) {
$data = $response->json();
}
} catch (\Illuminate\Http\Client\ConnectionException $e) {
echo "连接失败: " . $e->getMessage();
} catch (\Illuminate\Http\Client\RequestException $e) {
echo "请求错误: " . $e->getMessage();
}
最佳实践建议
超时时间设置建议
| 场景 | 连接超时 | 请求超时 |
|---|---|---|
| 内部服务 | 2秒 | 5秒 |
| 外部API | 5秒 | 10秒 |
| 大数据处理 | 10秒 | 30秒 |
| 长时间任务 | 15秒 | 60秒 |
错误处理策略
<?php
class NetworkTimeoutHandler {
/**
* 分级重试策略
*/
public function retryWithBackoff($callback, $options = []) {
$maxRetries = $options['max_retries'] ?? 3;
$baseDelay = $options['base_delay'] ?? 1;
$useExponentialBackoff = $options['exponential'] ?? true;
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
try {
return $callback();
} catch (Exception $e) {
if ($attempt < $maxRetries - 1) {
// 计算延迟时间
if ($useExponentialBackoff) {
$delay = $baseDelay * pow(2, $attempt);
} else {
$delay = $baseDelay;
}
// 添加随机抖动,防止同步重试
$delay = $delay * (0.5 + lcg_value() * 0.5);
sleep($delay);
}
}
}
throw new Exception("请求失败,重试次数已用完");
}
/**
* 请求降级处理
*/
public function withFallback($primaryCallback, $fallbackCallback) {
try {
return $primaryCallback();
} catch (TimeoutException $e) {
// 主请求超时,启用备用方案
return $fallbackCallback();
}
}
/**
* 批量请求超时处理
*/
public function batchRequestWithTimeout($urls, $timeout = 5) {
$results = [];
$multiHandle = curl_multi_init();
$handles = [];
foreach ($urls as $key => $url) {
$handle = curl_init($url);
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $timeout/2,
]);
$handles[$key] = $handle;
curl_multi_add_handle($multiHandle, $handle);
}
$active = null;
do {
$mrc = curl_multi_exec($multiHandle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($multiHandle) == -1) {
usleep(100);
}
do {
$mrc = curl_multi_exec($multiHandle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
foreach ($handles as $key => $handle) {
$results[$key] = curl_multi_getcontent($handle);
curl_multi_remove_handle($multiHandle, $handle);
curl_close($handle);
}
curl_multi_close($multiHandle);
return $results;
}
}
// 使用示例
$handler = new NetworkTimeoutHandler();
try {
$result = $handler->retryWithBackoff(function() {
$client = new GuzzleClient(['timeout' => 5]);
return $client->get('https://api.example.com');
}, [
'max_retries' => 3,
'base_delay' => 1,
'exponential' => true
]);
} catch (Exception $e) {
// 降级处理
$fallbackData = $handler->withFallback(
function() { return fetchFromCache(); },
function() { return fetchFromDatabase(); }
);
}
监控和日志记录
<?php
/**
* 超时监控
*/
class TimeoutMonitor {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* 记录超时事件
*/
public function recordTimeout($url, $timeout) {
$this->redis->lpush('timeout_log', json_encode([
'url' => $url,
'timeout' => $timeout,
'timestamp' => date('Y-m-d H:i:s')
]));
// 记录到监控系统
$this->sendToMonitor($url, $timeout);
}
/**
* 获取超时统计
*/
public function getTimeoutStats() {
$logs = $this->redis->lrange('timeout_log', 0, 99);
$stats = ['total' => 0, 'by_url' => []];
foreach ($logs as $log) {
$logData = json_decode($log, true);
$stats['total']++;
if (!isset($stats['by_url'][$logData['url']])) {
$stats['by_url'][$logData['url']] = 0;
}
$stats['by_url'][$logData['url']]++;
}
return $stats;
}
/**
* 发送到监控系统
*/
private function sendToMonitor($url, $timeout) {
// 集成 Sentry、Elastic APM 等
$data = [
'type' => 'timeout',
'url' => $url,
'timeout' => $timeout,
'timestamp' => time()
];
// PS:实际项目中应使用专业监控框架
file_put_contents('/var/log/timeout_monitor.log',
json_encode($data) . PHP_EOL, FILE_APPEND);
}
}
- 选择合适的超时时间:根据业务场景动态调整
- 实现重试机制:使用指数退避算法,避免雪崩
- 做好降级处理:准备备用方案
- 完善监控告警:及时发现并解决问题
- 合理设计架构:考虑使用消息队列、异步处理等
这些方案可以根据你的项目需求进行选择和调整,核心是:合理设置超时时间 + 实现优雅的错误处理 + 做好日志记录。