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;
}
排查方法:

# 查看 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
最佳实践建议
-
分层设置超时:Nginx (60s) > PHP-FPM (30s) > 数据库查询 (5s) > 外部API (2s)
-
统一超时处理:在应用入口设置全局超时拦截
// Bootstrap 文件中
set_time_limit(55); // 略小于 PHP-FPM 的 60s
register_shutdown_function(function() {
if (connection_aborted()) {
error_log('Request timed out at: ' . $_SERVER['REQUEST_URI']);
}
});
-
使用消息队列:对于长时间任务,改用异步处理(如 RabbitMQ、Redis Queue)
-
合理拆分功能:大查询改为小批量多次查询
-
定期优化:使用 OPcache、优化 SQL、使用缓存(Redis/Memcached)
通过以上方法,可以快速定位 504 超时的原因,并根据具体情况进行优化,记住关键点是:先看日志,再改配置,最后验证效果。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播