一个诡异的502错误
上周三凌晨3点,手机钉钉突然炸锅——客户反馈网站打不开,直接报502,我打开宝塔后台,Nginx状态栏显示“上游服务器无响应”,但PHP-FPM进程池显示空闲进程还有十几个,这种“看起来一切正常,实际已经崩了”的诡异情况,最磨人。
当时我第一反应是MySQL卡死了,重启数据库没奏效;又怀疑是OPcache缓存堵塞,清空缓存依然502,折腾了20分钟,后台CPU占用率却只有15%,内存还剩2G,实在没头绪。
宝塔Nginx日志里藏着鬼打墙?三分钟定位慢查询与恶意扫描
日志里的“鬼影”:一个请求重复了384次
终于在凌晨4点,我点开了宝塔面板的“Nginx日志”功能,勾选了“错误日志”并筛选“error”,结果发现一条触目惊心的记录:
[error] 12345#0: *384 upstream timed out (110: Connection timed out) while reading response header from upstream
注意那个“*384”——这代表同一个请求ID被重试了384次!Nginx在持续向PHP-FPM发起同一个请求,直到超时,我立刻意识到:这是某个耗时的上游请求“堵门”了。
接着切换到“访问日志”,按时间倒序筛选:
168.1.100 - - [03:15:23] "POST /api/export-data HTTP/1.1" 502 0.03
192.168.1.100 - - [03:15:24] "POST /api/export-data HTTP/1.1" 502 0.03
同一个IP(内网),同一个接口,每秒一次请求,整整持续了15分钟,更骚的是,这个请求的响应时间只有0.03秒就报了502——说明不是PHP执行慢,而是PHP根本就没有处理它。
排查思路:先锁定是谁在“堵门”
第一步:查Nginx日志中的“上游队列”
用宝塔日志的“实时监控”功能,发现Nginx的workers进程数飙升到32个(默认8个),且全部卡在“等待上游响应”状态,这说明Nginx把请求都发给了PHP-FPM,但PHP-FPM的处理线程被某个请求“霸占”了。
第二步:查PHP-FPM的慢日志
在宝塔面板->软件商店->PHP设置中开启“慢日志”并设置为1秒,5分钟后日志里出现:
[pool www] pid 12345
script_filename = /www/wwwroot/site/api/export-data.php
request_uri = “POST /api/export-data”
execution_time = 32.3s
一个导出数据的接口执行了32秒!但502是在0.03秒出现的——这说明PHP-FPM的进程池被占满了,新请求进来发现没有空闲worker,直接报503/502。
第三步:看系统资源
用top -H -p <php-fpm主进程号>查看,发现所有PHP子进程都处于D状态(不可中断睡眠),说明在等待I/O。strace -p <进程号>跟了一下,发现所有进程都在等待同一个锁——写文件锁,原来那个导出接口在循环里反复fwrite一个临时文件,因为没及时flock释放锁,把整个进程池的写操作堵死了。
解决方案:三刀砍死“慢查询妖”
第一刀:干掉堵门请求
直接kill掉导致问题的PHP-FPM子进程(kill -9 12345),502瞬间恢复,但治标不治本,那个接口还是会在下次请求时复现。
第二刀:限制Nginx重试次数
在宝塔面板->网站设置->配置文件里加:
proxy_next_upstream off;
proxy_next_upstream_timeout 0;
默认Nginx会尝试转发给下一个PHP-FPM进程,但配置不当会导致无限重试,关闭重试后,502直接返回客户端,不会拖垮整个服务。
第三刀:锁死超时时间和并发数
在PHP-FPM配置(/www/server/php/74/etc/php-fpm.conf)里改两个参数:
request_terminate_timeout = 30s // 超30秒直接终止
pm.max_children = 20 // 控制最大进程数,防止被恶意请求打爆
同时给那个导出接口加队列限流:写一个shell脚本每分钟检查netstat -anp|grep :9000|wc -l,如果超过15个连接,就自动重启PHP-FPM并报警。
预防建议:日常养护三条铁律
-
每天凌晨3点自动分析Nginx访问日志
用宝塔计划任务加一行:tail -n 1000 /www/wwwlogs/网站.access.log | awk '$11 > 5 {print $0}' | sort -k 11 -rn > /tmp/slow.log,筛选出响应时间超过5秒的请求,自动邮件推送给运维,这样第二天上班就能发现异常接口。 -
开启Nginx日志中的“请求处理耗时”字段
在宝塔网站配置的log_format里加上$request_time $upstream_response_time,这样日志里就能看到“请求一共花了多少秒”和“PHP执行花了多少秒”,比如发现23 0.01,说明1.22秒卡在Nginx到PHP的传输上,要查防火墙或端口连通性。 -
给Nginx日志做“轮询切割”并设置保留7天
宝塔默认日志会无限膨胀,一个网站正常流量一天能写几百MB日志,在计划任务里加:mv /www/wwwlogs/网站.access.log /www/wwwlogs/网站.access-$(date +%Y%m%d).log kill -USR1 `cat /www/server/nginx/logs/nginx.pid`同时只保留最近7天的:
find /www/wwwlogs -name “*access*” -mtime +7 -delete,避免磁盘被填满,还能快速翻查近期问题。
最后说句大实话
Nginx日志不是给你看的,是给系统“自检”用的,当你发现某天日志突然变少,或者某个接口的响应时间突然从0.1秒飙到5秒,哪怕只持续了2分钟——说明已经有人踩坑了,不要等到502报警再查日志,让日志帮你提前预警,才算真正发挥了它的价值。



发表评论