PHP项目怎样处理超时请求?

wen PHP项目 13

本文目录导读:

PHP项目怎样处理超时请求?

  1. 脚本执行超时控制
  2. HTTP请求超时控制
  3. 数据库查询超时
  4. 服务器层面配置
  5. 超时处理最佳实践
  6. 监控与告警
  7. 常见问题及解决方案

在PHP项目中处理超时请求,通常需要从应用层服务器层客户端层三个维度进行控制,以下是具体的实现方案:

脚本执行超时控制

使用 set_time_limit()

在脚本开头设置最大执行时间:

// 设置脚本最大执行时间为30秒
set_time_limit(30);
// 或者动态调整
$timeout = 60;
set_time_limit($timeout);

使用 register_shutdown_function() 捕获超时

register_shutdown_function(function() {
    $error = error_get_last();
    if ($error && $error['type'] === E_ERROR) {
        // 处理超时错误
        http_response_code(504);
        echo json_encode(['error' => '请求超时']);
    }
});
set_time_limit(5);
// 模拟耗时操作
sleep(10); // 这里会触发超时

HTTP请求超时控制

cURL 超时设置

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => 'http://example.com/api',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10,  // 连接超时(秒)
    CURLOPT_TIMEOUT => 30,         // 总超时(秒)
    CURLOPT_TIMEOUT_MS => 30000,   // 毫秒级超时 (PHP 5.2.3+)
]);
$response = curl_exec($ch);
if (curl_errno($ch) == CURLE_OPERATION_TIMEDOUT) {
    // 处理超时
    echo "请求超时";
}
curl_close($ch);

file_get_contents() 超时控制

$opts = [
    'http' => [
        'method' => 'GET',
        'timeout' => 30,  // 秒
    ]
];
$context = stream_context_create($opts);
$result = @file_get_contents('http://example.com', false, $context);

Guzzle HTTP 客户端超时

use GuzzleHttp\Client;
$client = new Client([
    'timeout' => 30.0,          // 响应超时
    'connect_timeout' => 10.0,  // 连接超时
    'read_timeout' => 30.0,     // 读取超时
    'write_timeout' => 30.0,    // 写入超时
]);
try {
    $response = $client->get('http://example.com');
} catch (\GuzzleHttp\Exception\ConnectException $e) {
    // 连接超时
} catch (\GuzzleHttp\Exception\RequestException $e) {
    // 请求超时
}

数据库查询超时

MySQL 查询超时

// PDO
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->exec("SET SESSION MAX_EXECUTION_TIME = 5000"); // 毫秒
// mysqli
$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$mysqli->query("SET SESSION MAX_EXECUTION_TIME = 5000");

Redis 操作超时

$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 2.5); // 连接超时2.5秒
$redis->setOption(Redis::OPT_READ_TIMEOUT, 5); // 读取超时5秒
try {
    $value = $redis->get('key');
} catch (RedisException $e) {
    // 处理超时
}

服务器层面配置

Nginx 配置

# 在 server 或 location 块中配置
location ~ \.php$ {
    fastcgi_read_timeout 300;      # FastCGI 超时时间(秒)
    fastcgi_connect_timeout 60;    # 连接超时
    fastcgi_send_timeout 300;      # 发送超时
    proxy_read_timeout 300;        # 代理读取超时
}

Apache 配置

# httpd.conf 或 .htaccess
<IfModule mod_fcgid.c>
    FcgidIOTimeout 300
    FcgidBusyTimeout 300
    FcgidConnectTimeout 60
</IfModule>
# PHP-FPM 配置
<IfModule mod_proxy_fcgi.c>
    ProxyTimeout 300
</IfModule>

PHP-FPM 配置

; php-fpm.conf 或 pool.d/www.conf
request_terminate_timeout = 300   ; 请求终止超时
request_slowlog_timeout = 30     ; 慢请求记录阈值
slowlog = /var/log/php-fpm/slow.log
max_execution_time = 300
max_input_time = 300

超时处理最佳实践

队列处理长任务

// 使用消息队列处理耗时任务
class AsyncTaskProcessor {
    public function handleLongTask($data) {
        // 1. 将任务推入队列
        $queue->push('process_task', $data);
        // 2. 立即返回处理中状态
        return ['status' => 'processing', 'task_id' => $taskId];
    }
    // 3. 后台 worker 处理实际逻辑
    public function processTask($data) {
        set_time_limit(0); // 无超时限制
        // 执行耗时操作...
    }
}

使用进程管理工具

// 使用 pcntl_fork 处理超时
$pid = pcntl_fork();
if ($pid == -1) {
    die('Could not fork');
} elseif ($pid) {
    // 父进程:设置超时计时器
    $timeout = 30;
    sleep($timeout);
    // 如果子进程还在运行,发送终止信号
    posix_kill($pid, SIGTERM);
    pcntl_wait($status);
} else {
    // 子进程:执行实际任务
    set_time_limit(0);
    // 执行耗时操作...
    exit(0);
}

优雅的超时处理

class TimeoutHandler {
    private $startTime;
    private $timeout;
    public function __construct($timeout = 30) {
        $this->startTime = time();
        $this->timeout = $timeout;
    }
    public function checkTimeout() {
        if (time() - $this->startTime > $this->timeout) {
            throw new TimeoutException('操作超时');
        }
    }
    public function executeWithTimeout(callable $callback) {
        while (true) {
            $this->checkTimeout();
            $result = $callback();
            if ($result !== null) {
                return $result;
            }
            usleep(100000); // 100ms
        }
    }
}
// 使用示例
$handler = new TimeoutHandler(10);
try {
    $result = $handler->executeWithTimeout(function() {
        // 执行需要超时控制的操作
        return checkStatus();
    });
} catch (TimeoutException $e) {
    // 处理超时
    echo $e->getMessage();
}

监控与告警

记录超时日志

class TimeoutLogger {
    public static function log($context) {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'unknown',
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
            'timeout' => $context['timeout'],
            'execution_time' => $context['execution_time']
        ];
        error_log(json_encode($logData) . "\n", 3, '/var/log/php_timeout.log');
    }
}

使用性能监控工具

  • Xdebug:跟踪执行时间和内存使用
  • Blackfire.io:性能分析
  • New Relic:应用性能监控

常见问题及解决方案

问题 解决方案
文件上传超时 使用分片上传或异步处理
大量数据处理超时 使用分页或游标查询
第三方API响应慢 设置合理的超时时间,使用缓存
复杂计算超时 使用生成器或流式处理

推荐策略:对于新项目,建议使用队列系统(如RabbitMQ、Redis Queue)处理耗时操作,前端配合轮询或WebSocket获取结果,这比单纯增加超时时间更健壮。

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