You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

批量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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 02:37:08