本文目录导读:

在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(); // 恢复默认 }
超时处理的关键是早检测、早处理、有记录、有兜底,根据你的项目类型选择合适的处理方式。