本文目录导读:

在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);
}
}
- 选择合适的超时时间:根据业务场景动态调整
- 实现重试机制:使用指数退避算法,避免雪崩
- 做好降级处理:准备备用方案
- 完善监控告警:及时发现并解决问题
- 合理设计架构:考虑使用消息队列、异步处理等
这些方案可以根据你的项目需求进行选择和调整,核心是:合理设置超时时间 + 实现优雅的错误处理 + 做好日志记录。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播