PHP项目怎么处理请求超时异常?

wen PHP项目 9

本文目录导读:

PHP项目怎么处理请求超时异常?

  1. PHP脚本执行时间限制
  2. 数据库查询超时
  3. cURL请求超时
  4. HTTP请求超时(使用Guzzle)
  5. 进程管理方案
  6. 异步处理和队列方案
  7. 全局超时中间件(框架示例)
  8. 最佳实践建议
  9. 总结建议

在PHP项目中处理请求超时异常,可以从以下几个层面进行:

PHP脚本执行时间限制

设置执行时间

// 设置无限制执行时间(不安全)
set_time_limit(0);
// 设置具体秒数
set_time_limit(300); // 5分钟
// 也可以在php.ini中设置
; max_execution_time = 300

捕获超时异常

try {
    // 某些耗时操作
    $result = someTimeConsumingOperation();
} catch (Exception $e) {
    if (strpos($e->getMessage(), 'Maximum execution time') !== false) {
        // 处理超时异常
        error_log('Script execution timed out: ' . $e->getMessage());
        http_response_code(503);
        echo json_encode(['error' => '请求超时,请稍后重试']);
    } else {
        throw $e; // 重新抛出其他异常
    }
}

数据库查询超时

MySQL查询超时

// 设置MySQL查询超时(5秒)
$pdo = new PDO($dsn, $user, $pass);
$pdo->exec("SET SESSION max_execution_time = 5000");
// 或使用PDO Statement超时
$stmt = $pdo->prepare("SELECT * FROM large_table");
$stmt->execute();

Redis操作超时

$redis = new Redis();
try {
    $redis->connect('127.0.0.1', 6379, 2.5); // 2.5秒连接超时
    $redis->setOption(Redis::OPT_READ_TIMEOUT, 5); // 5秒读取超时
    $result = $redis->get('some_key');
} catch (RedisException $e) {
    error_log('Redis timeout: ' . $e->getMessage());
    // 降级处理
}

cURL请求超时

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.example.com',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10, // 连接超时10秒
    CURLOPT_TIMEOUT => 30,        // 总超时30秒
    CURLOPT_TIMEOUT_MS => 30000,  // 毫秒单位
]);
$response = curl_exec($ch);
if (curl_errno($ch) == CURLE_OPERATION_TIMEDOUT) {
    error_log('cURL request timed out');
    echo "请求超时,请稍后重试";
}
curl_close($ch);

HTTP请求超时(使用Guzzle)

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\TransferException;
$client = new Client([
    'timeout' => 10,        // 总超时
    'connect_timeout' => 5, // 连接超时
    'read_timeout' => 30,   // 读取超时
]);
try {
    $response = $client->get('https://api.example.com/data');
} catch (ConnectException $e) {
    error_log('Connection timeout: ' . $e->getMessage());
} catch (TransferException $e) {
    error_log('Transfer timeout: ' . $e->getMessage());
}

进程管理方案

使用pcntl实现超时控制

declare(ticks = 1);
// 设置超时信号
function timeout_handler($signo) {
    throw new \RuntimeException('Process timeout', 408);
}
pcntl_signal(SIGALRM, 'timeout_handler');
pcntl_alarm(30); // 30秒超时
try {
    // 耗时操作
    longRunningProcess();
    pcntl_alarm(0); // 取消闹钟
} catch (\RuntimeException $e) {
    if ($e->getCode() == 408) {
        error_log('Process timed out');
        // 降级处理
    }
}

异步处理和队列方案

使用消息队列处理长任务

// 1. 提交任务到队列
$jobId = $queue->push(new LongRunningJob($data));
// 2. 返回立即响应
echo json_encode([
    'status' => 'accepted',
    'job_id' => $jobId,
    'message' => '任务已提交,稍后查看结果'
]);
// 3. 后台Worker处理
class LongRunningJob
{
    public function handle()
    {
        try {
            set_time_limit(600); // 允许10分钟
            // 处理任务...
        } catch (\Exception $e) {
            Log::error('Job timeout: ' . $e->getMessage());
            // 重试或记录失败
        }
    }
}

全局超时中间件(框架示例)

Laravel/Symfony中间件

namespace App\Http\Middleware;
use Closure;
use Symfony\Component\HttpKernel\Exception\HttpException;
class RequestTimeout
{
    public function handle($request, Closure $next, $timeout = 30)
    {
        // 设置最大执行时间
        set_time_limit($timeout);
        try {
            $response = $next($request);
        } catch (\Exception $e) {
            if (strpos($e->getMessage(), 'Maximum execution time') !== false) {
                throw new HttpException(504, 'Request timeout');
            }
            throw $e;
        }
        return $response;
    }
}

最佳实践建议

分层超时设置

class TimeoutHandler
{
    const DEFAULT_TIMEOUT = 30;
    const DB_TIMEOUT = 5;
    const API_TIMEOUT = 10;
    const FILE_TIMEOUT = 60;
    public static function handle(callable $callback, $timeout = self::DEFAULT_TIMEOUT)
    {
        $startTime = microtime(true);
        try {
            $result = $callback();
            $elapsed = microtime(true) - $startTime;
            if ($elapsed > $timeout) {
                throw new TimeoutException("Operation exceeded {$timeout}s");
            }
            return $result;
        } catch (TimeoutException $e) {
            // 记录慢查询
            Log::warning('Slow operation detected', [
                'timeout' => $timeout,
                'elapsed' => $elapsed ?? 0
            ]);
            // 返回降级数据
            return self::getFallbackData();
        }
    }
    private static function getFallbackData()
    {
        return ['error' => '请求超时', 'status' => 'timeout'];
    }
}
// 使用
$result = TimeoutHandler::handle(function() {
    return $api->getData();
}, TimeoutHandler::API_TIMEOUT);

超时监控和告警

// 记录超时事件用于监控
$timeoutMetrics = [
    'timestamp' => time(),
    'endpoint' => $_SERVER['REQUEST_URI'],
    'method' => $_SERVER['REQUEST_METHOD'],
    'timeout_seconds' => $timeout,
];
// 发送到监控系统
Metrics::increment('request.timeout', $timeoutMetrics);

总结建议

  1. 区分超时类型:连接超时、响应超时、执行超时等
  2. 设置合理超时:根据业务场景调整(API调用5-10s,文件处理30-60s)
  3. 优雅降级:超时时返回友好的错误信息或缓存数据
  4. 异步处理:长任务使用消息队列异步执行
  5. 监控告警:记录超时事件并设置告警
  6. 前端配合:前端设置合理的AJAX超时并显示加载状态
  7. 避免无限等待:所有外部调用必须设置超时

选择哪种方案取决于你的具体业务需求和系统架构,如果问题依然存在,建议排查慢查询、网络延迟、资源竞争等根本原因。

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