PHP项目执行超时如何代码内捕获处理

wen PHP项目 23

本文目录导读:

PHP项目执行超时如何代码内捕获处理

  1. PHP 脚本执行超时 (max_execution_time)
  2. HTTP 请求超时
  3. 数据库查询超时
  4. 实用封装:统一超时处理类
  5. 最佳实践建议

在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();
}

最佳实践建议

  1. 分层处理

    • try-catch 块中处理逻辑超时
    • 使用 register_shutdown_function 作为兜底
    • 记录详细的超时日志
  2. 优雅降级

    try {
        // 尝试执行可能超时的操作
        slowOperation();
    } catch (TimeoutException $e) {
        // 提供降级方案
        return getCachedResult();
    }
  3. 限制粒度

    // 不要对整个脚本设置太长的超时时间
    // 而是要针对特定操作设置超时
    function riskyOperation() {
        set_time_limit(5);
        // 风险操作
        restore_time_limit(); // 恢复默认
    }

超时处理的关键是早检测、早处理、有记录、有兜底,根据你的项目类型选择合适的处理方式。

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