批量PHP进程调度脚本随机挂起/被系统终止问题排查与修复
问题描述
有大约30条如下格式的PHP命令:
nohup php /home/u642830940/domains/domain.com/public_html/admin.php -p --dispatch=advanced_import.import.import --preset_id=$preset_id > /dev/null
同时编写了bash调度脚本用于控制并发数:
#!/bin/bash commands_str=$(/usr/bin/php /home/u642830940/domains/idata.lv/public_html/parsing/spark cron1 foxway) IFS='&' read -ra commands <<< "$(echo "$commands_str" | tail -n +4)" max_commands=7 for command in "${commands[@]}"; do commands_total=$(ps aux | grep php | wc -l || printf "0") while [ $commands_total -ge $max_commands ]; do if [ $commands_total -ge $max_commands ]; then echo -e "maximum number of commands reached: ${commands_total}/${max_commands}. waiting... \n" fi wait -n commands_total=$(ps aux | grep php | wc -l || printf "0") done echo -e "starting new command: $command \n" bash -c "$command 2>&1 & disown" done
但脚本会随机出现挂起或被系统终止的情况,间隔从2小时到1天不等。单独执行这些命令无异常(最长单命令耗时约40分钟),需要分析问题原因并给出修复方案。
原因分析
- 进程统计误差:
ps aux | grep php会将执行grep的自身进程计入统计,导致进程数计算偏多,可能让脚本错误进入等待逻辑,甚至无限等待。 wait -n兼容性与逻辑缺陷:部分旧版本bash不支持wait -n;当没有后台子进程可等待时,wait -n会返回错误,若未处理会引发脚本挂起或终止。- 子进程跟踪丢失:使用
disown后,脚本无法跟踪子进程状态,wait -n无法正确等待子进程结束,导致等待逻辑失效。 - 系统资源限制:若进程占用过多内存/CPU,可能触发系统OOM Killer直接终止进程;若通过cron启动脚本,部分系统的cron会限制长时间运行的脚本。
- 命令解析风险:用
&作为命令分隔符,若生成的命令本身包含&或特殊字符,会导致解析错误,执行异常命令引发脚本崩溃。
修复方案
1. 修正进程统计逻辑
替换不准确的进程统计命令,改用pgrep或排除grep自身进程的方式:
# 方式1:用pgrep统计PHP进程数(推荐) commands_total=$(pgrep -c php || printf "0") # 方式2:排除grep自身进程 commands_total=$(ps aux | grep -v grep | grep -c php || printf "0")
2. 改进子进程跟踪与等待机制
去掉disown,维护一个运行中PID的列表,精准跟踪脚本启动的子进程,避免统计系统中其他无关PHP进程:
#!/bin/bash # 读取命令列表(优化解析方式,避免&分隔的风险) commands=() while IFS= read -r line; do commands+=("$line") done < <(/usr/bin/php /home/u642830940/domains/idata.lv/public_html/parsing/spark cron1 foxway | tail -n +4) max_commands=7 running_pids=() for command in "${commands[@]}"; do # 清理已结束的PID running_pids=($(for pid in "${running_pids[@]}"; do if kill -0 "$pid" 2>/dev/null; then echo "$pid" fi done)) # 等待直到有空闲并发位 while [ "${#running_pids[@]}" -ge "$max_commands" ]; do echo "maximum number of commands reached: ${#running_pids[@]}/${max_commands}. waiting..." wait -n # 再次清理已结束的PID running_pids=($(for pid in "${running_pids[@]}"; do if kill -0 "$pid" 2>/dev/null; then echo "$pid" fi done)) done echo "starting new command: $command" # 启动进程并记录PID bash -c "$command 2>&1" & running_pids+=("$!") done # 等待所有剩余进程完成 wait
3. 兼容低版本bash的等待逻辑
如果系统bash版本低于4.3(不支持wait -n),用定时检查PID状态的方式替代:
# 替换原while等待循环部分 while [ "${#running_pids[@]}" -ge "$max_commands" ]; do echo "maximum number of commands reached: ${#running_pids[@]}/${max_commands}. waiting..." sleep 10 # 每隔10秒检查一次进程状态 # 清理已结束的PID running_pids=($(for pid in "${running_pids[@]}"; do if kill -0 "$pid" 2>/dev/null; then echo "$pid" fi done)) done
4. 排查系统资源与运行环境
- 检查系统日志(
/var/log/syslog或/var/log/messages),搜索关键词Out of memory确认是否触发OOM Killer,若存在需优化PHP命令的内存占用。 - 若脚本通过cron启动,改用
nohup启动脚本以避免cron的超时限制:
nohup /path/to/your/scheduler.sh > /var/log/scheduler.log 2>&1 &
5. 优化命令解析方式
避免使用&作为命令分隔符,让生成命令的PHP脚本输出每行一条命令,再通过循环读取,避免特殊字符解析错误:
# 读取命令列表,每行一条命令 commands=() while IFS= read -r line; do commands+=("$line") done < <(/usr/bin/php /home/u642830940/domains/idata.lv/public_html/parsing/spark cron1 foxway | tail -n +4)
内容的提问来源于stack exchange,提问作者Oleg Kosarev
相关产品推荐
相关产品推荐

