PHP 504超时定位

wen PHP项目 1

PHP 504 超时定位排查指南

快速定位流程图

504 错误发生
     ↓
1. 确认是哪个环节超时?
   ├─ Nginx 层超时
   ├─ PHP-FPM 层超时
   ├─ 外部请求超时(数据库/API/文件)
   └─ 业务逻辑死循环/慢查询
     ↓
2. 查看错误日志
   ├─ Nginx error.log
   ├─ PHP-FPM error.log
   └─ 应用日志(laravel.log等)
     ↓
3. 定位具体代码
   ├─ 访问日志时间分析
   ├─ Xdebug 或 Trace 追踪
   └─ 分段记录执行时间

各层级超时配置与排查

Nginx 层(最常见的 504 来源)

# nginx.conf 或站点配置
server {
    # 读取请求超时(默认60s)
    client_body_timeout 30s;
    # 发送响应超时(默认60s)
    send_timeout 30s;
    # 代理超时(PHP-FPM)
    proxy_connect_timeout 30s;
    proxy_read_timeout 120s;
    proxy_send_timeout 60s;
    # FastCGI 超时(PHP-FPM)
    fastcgi_connect_timeout 30s;
    fastcgi_read_timeout 120s;
    fastcgi_send_timeout 30s;
}

排查方法:

PHP 504超时定位

# 查看 Nginx 错误日志
tail -f /var/log/nginx/error.log
# 查看访问日志,找 504 状态码
grep "504" /var/log/nginx/access.log | tail -20

PHP-FPM 层

# php.ini
max_execution_time = 30
max_input_time = 60
# php-fpm.conf 或 www.conf
request_terminate_timeout = 60
request_slowlog_timeout = 10
slowlog = /var/log/php-fpm/slow.log

排查方法:

# 查看 PHP-FPM 错误日志
tail -f /var/log/php-fpm/error.log
# 查看慢日志(非常有用)
cat /var/log/php-fpm/slow.log
# 查看当前 PHP-FPM 进程状态
ps aux | grep php-fpm

应用层定位(核心步骤)

添加执行时间日志
// 在入口文件(如 index.php)开头
$start_time = microtime(true);
$start_memory = memory_get_usage();
// 在文件末尾
file_put_contents('/tmp/php_timing.log', 
    date('Y-m-d H:i:s') . ' | Time: ' . 
    (microtime(true) - $start_time) . 's | Memory: ' . 
    (memory_get_usage() - $start_memory) / 1024 . 'KB\n', 
    FILE_APPEND
);
分步记录(定位到具体函数)
// 创建一个日志辅助函数
function debug_trace($msg) {
    file_put_contents('/tmp/debug.log', 
        date('H:i:s') . ' | ' . $msg . ' | Memory: ' . 
        memory_get_usage()/1024/1024 . 'MB\n', 
        FILE_APPEND);
}
// 在业务代码中分段记录
debug_trace('开始执行');
$result = $db->query("SELECT * FROM big_table"); // 这里可能卡住
debug_trace('数据库查询完成');
$result2 = call_external_api(); // 这里可能卡住
debug_trace('外部API调用完成');
使用 Xdebug 构建火焰图
# php.ini 配置 Xdebug
xdebug.mode = profile
xdebug.output_dir = /tmp/xdebug
xdebug.max_nesting_level = 512
# 生成性能分析文件后,使用工具分析
# 安装 Webgrind 查看
git clone https://github.com/jokkedk/webgrind.git
# 将 webgrind/index.php 放到 web 目录,它会显示调用时间和次数

常见原因及解决方案

数据库慢查询

// 优化前
$users = $db->query("SELECT * FROM users WHERE created_at > NOW() - INTERVAL 1 YEAR");
// 优化后:添加索引
DB::statement('ALTER TABLE users ADD INDEX idx_created_at (created_at)');
// 使用分页代替全量查询
$users = $db->select("SELECT * FROM users LIMIT 100 OFFSET 0");

检查数据库:

-- 查看当前正在执行的查询
SHOW PROCESSLIST;
-- 查看慢查询日志
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';

外部 API 调用超时

// 设置超时时间
$options = [
    'timeout' => 10, // 10秒超时
    'connect_timeout' => 5,
];
// Guzzle
$client = new GuzzleHttp\Client($options);
// Curl
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
// 最佳实践:使用异步或添加重试机制

循环执行时间过长

// 优化前:全量处理
foreach ($huge_array as $item) {
    process($item); // 耗时操作
}
// 优化后:分批处理 + 分割时间
$chunks = array_chunk($huge_array, 100);
$processing_time = 0;
foreach ($chunks as $chunk) {
    $start = microtime(true);
    foreach ($chunk as $item) {
        process($item);
    }
    $processing_time += (microtime(true) - $start);
    // 如果用时过长,检查是否要在中间保存状态
    if ($processing_time > 25) {
        break; // 为避免超时,提前结束并保存进度
    }
}

大文件处理

// 优化前:全部读入内存
$data = file_get_contents('large_file.csv');
process($data);
// 优化后:流式处理
$handle = fopen('large_file.csv', 'r');
while (($row = fgetcsv($handle)) !== false) {
    process($row); // 逐行处理
}
fclose($handle);

监控与预防

实时监控脚本

#!/bin/bash
# monitor.sh - 实时监控 PHP-FPM 超时
while true; do
    clear
    echo "=== 当前 PHP-FPM 进程 ==="
    ps aux | grep php-fpm | grep -v grep
    echo -e "\n=== 最近1分钟超时请求 ==="
    tail -50 /var/log/nginx/error.log | grep "timeout"
    sleep 5
done

设置告警

// 在应用中加入超时告警
if ((microtime(true) - $start_time) > 25) {
    // 发送邮件或钉钉通知
    email_admin(
        'Performance Alert',
        "Request took " . (microtime(true) - $start_time) . " seconds"
    );
}

常用排查命令汇总

# 查看 PHP 进程执行时间
ps -eo pid,etime,comm | grep php-fpm
# 实时查看慢请求
watch -n 1 "grep 504 /var/log/nginx/access.log | tail -5"
# 查看数据库连接
mysql -e "SHOW PROCESSLIST;"
# 查看系统负载
uptime
top -bn1 | head -20
# 监控网络连接
netstat -pant | grep :80

最佳实践建议

  1. 分层设置超时:Nginx (60s) > PHP-FPM (30s) > 数据库查询 (5s) > 外部API (2s)

  2. 统一超时处理:在应用入口设置全局超时拦截

// Bootstrap 文件中
set_time_limit(55); // 略小于 PHP-FPM 的 60s
register_shutdown_function(function() {
    if (connection_aborted()) {
        error_log('Request timed out at: ' . $_SERVER['REQUEST_URI']);
    }
});
  1. 使用消息队列:对于长时间任务,改用异步处理(如 RabbitMQ、Redis Queue)

  2. 合理拆分功能:大查询改为小批量多次查询

  3. 定期优化:使用 OPcache、优化 SQL、使用缓存(Redis/Memcached)

通过以上方法,可以快速定位 504 超时的原因,并根据具体情况进行优化,记住关键点是:先看日志,再改配置,最后验证效果

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