本文目录导读:

在PHP项目中,执行超时通常分为两种:HTTP 请求超时 和 PHP 脚本执行超时,针对不同的场景,处理方法也不同。
PHP 脚本执行超时 (max_execution_time)
a. 使用 set_time_limit() 和 try-catch
PHP 默认不会抛出异常,而是直接致命错误,你需要将超时转换为异常。
try {
// 设置最大执行时间 5 秒
set_time_limit(5);
// 模拟耗时操作
$i = 0;
while ($i < 10000000) {
$i++;
// 模拟工作
}
echo "完成";
} catch (Error $e) {
// 捕获致命错误
echo "脚本执行超时: " . $e->getMessage();
// 记录日志
error_log("脚本执行超时: " . print_r($e, true));
// 清理资源
cleanup();
}
b. 自定义错误处理器(推荐)
将超时错误转换为异常,更符合现代 PHP 编程习惯。
// 注册一个错误处理器
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// 处理致命错误
if ($errno === E_ERROR || $errno === E_CORE_ERROR) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
return false;
});
try {
set_time_limit(3);
// 模拟死循环或耗时操作
while (true) {
usleep(100000);
}
} catch (ErrorException $e) {
// 捕获超时错误
echo "捕获到执行超时: " . $e->getMessage();
// 记录到日志
file_put_contents('timeout.log', date('Y-m-d H:i:s') . " - " . $e->getMessage() . "\n", FILE_APPEND);
}
c. 使用 register_shutdown_function() 检测(通用方案)
这个函数会在脚本结束时被调用,无论是否正常结束。
// 注册关闭函数
register_shutdown_function(function() {
$error = error_get_last();
if ($error !== null && $error['type'] === E_ERROR) {
// 检查是否是超时错误
if (strpos($error['message'], 'Maximum execution time') !== false) {
echo "检测到执行超时!";
// 执行清理操作
@session_write_close();
// 记录日志
writeToLog($error);
}
}
});
// 实际业务代码
set_time_limit(2);
while (true) {
// 无限循环导致超时
}
HTTP 请求超时
a. 使用 cURL 并设置超时时间
function makeRequest($url) {
$ch = curl_init();
// 设置超时时间
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 连接超时
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 执行超时
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
// 判断是否是超时错误
if (curl_errno($ch) === CURLE_OPERATION_TIMEDOUT) {
echo "请求超时: " . $error;
return false;
}
}
curl_close($ch);
return $response;
}
b. 使用 Guzzle HTTP 客户端
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'timeout' => 5.0, // 响应超时
'connect_timeout' => 3.0, // 连接超时
]);
try {
$response = $client->get('https://api.example.com');
echo $response->getBody();
} catch (ConnectException $e) {
// 连接超时
echo "连接超时: " . $e->getMessage();
// 重试逻辑
retryRequest();
} catch (RequestException $e) {
// 请求超时或其他错误
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
echo "HTTP 错误: " . $statusCode;
} else {
echo "请求失败: " . $e->getMessage();
}
}
数据库查询超时
MySQL 查询超时
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
// 设置 MySQL 查询超时(秒)
$pdo->exec("SET SESSION wait_timeout = 10");
$pdo->exec("SET SESSION max_execution_time = 5000"); // MySQL 8.0+
// 或者设置锁等待超时
$pdo->exec("SET SESSION innodb_lock_wait_timeout = 5");
// 执行可能超时的查询
$stmt = $pdo->query("SELECT SLEEP(20)"); // 模拟长时间查询
} catch (PDOException $e) {
// 检查错误代码
if ($e->getCode() === 'HY000') {
echo "查询超时: " . $e->getMessage();
} else {
echo "数据库错误: " . $e->getMessage();
}
}
实用封装:统一超时处理类
class TimeoutHandler {
private $maxExecutionTime;
private $startTime;
public function __construct($timeoutSeconds = 30) {
$this->maxExecutionTime = $timeoutSeconds;
$this->startTime = time();
// 注册关闭函数
register_shutdown_function([$this, 'onShutdown']);
// 设置 PHP 执行时间
set_time_limit($timeoutSeconds + 5); // 额外加 5 秒缓冲
}
/**
* 检查是否超时
*/
public function isTimeout(): bool {
return (time() - $this->startTime) >= $this->maxExecutionTime;
}
/**
* 带超时检查的睡眠
*/
public function sleepWithTimeout($microseconds = 100000) {
if ($this->isTimeout()) {
throw new TimeoutException("操作已超时");
}
usleep($microseconds);
}
/**
* 关闭时检查
*/
public function onShutdown() {
$error = error_get_last();
if ($error && $error['type'] === E_ERROR) {
$this->handleTimeoutError($error);
}
}
private function handleTimeoutError($error) {
// 记录超时日志
$logData = [
'time' => date('Y-m-d H:i:s'),
'error' => $error['message'],
'file' => $error['file'],
'line' => $error['line']
];
file_put_contents(
'timeout_logs.txt',
json_encode($logData) . "\n",
FILE_APPEND
);
// 返回错误响应
if (!headers_sent()) {
http_response_code(503);
echo json_encode(['error' => '请求超时']);
}
}
}
// 使用示例
try {
$handler = new TimeoutHandler(10);
while (!$handler->isTimeout()) {
// 执行任务
processItem();
$handler->sleepWithTimeout(500000); // 0.5秒
}
echo "任务完成";
} catch (TimeoutException $e) {
echo "任务超时: " . $e->getMessage();
}
最佳实践建议
-
分层处理:
- 在
try-catch块中处理逻辑超时 - 使用
register_shutdown_function作为兜底 - 记录详细的超时日志
- 在
-
优雅降级:
try { // 尝试执行可能超时的操作 slowOperation(); } catch (TimeoutException $e) { // 提供降级方案 return getCachedResult(); } -
限制粒度:
// 不要对整个脚本设置太长的超时时间 // 而是要针对特定操作设置超时 function riskyOperation() { set_time_limit(5); // 风险操作 restore_time_limit(); // 恢复默认 }
超时处理的关键是早检测、早处理、有记录、有兜底,根据你的项目类型选择合适的处理方式。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播