Linux 下让 ThinkPHP 8 队列脱离终端后台常驻(start/status/stop 三脚本版)
在服务器上跑队列消费时,如果直接前台执行 php think queue:work,一旦断开 SSH 会话进程就可能被挂断,导致队列停止消费。本文提供一套轻量的脚本方案(基于 nohup + &),实现队列 脱离终端常驻 + 可启停 + 可查状态。
适用版本:ThinkPHP 8.x + topthink/think-queue 3.0+
说明:脚本中的PHP_BIN、PROJECT等已用假数据,使用时请改成你的实际路径。
目标效果
- ✅ 脱离终端:SSH 断开后队列仍继续运行
- ✅ 可配置:队列名、重试次数、worker 数量、日志路径等均可变量控制
- ✅ 防误杀:可选按项目目录严格匹配进程(避免误杀同机器其它项目的队列 worker)
- ✅ 可观测:标准输出/错误写入日志文件
- ✅ 可运维:一键启动、查看、停止
完整脚本代码(直接可用)
把下面三段分别保存为 start-queue.sh、status-queue.sh、stop-queue.sh(建议放项目根目录或 bin/ 目录)。
1) start-queue.sh - 启动队列(支持多 worker)
#!/usr/bin/env bash
#============= 用户配置区(改这里即可) =============
PHP_BIN=/www/server/php/81/bin/php # PHP 可执行文件路径
PROJECT=/www/wwwroot/example.com # 项目根目录
QUEUE_NAME=default # 队列名
TRIES=3 # 任务失败重试次数(根据业务调整)
WORKER_NUM=1 # worker 进程数量(多个 worker 可并行消费,建议 1-4)
LOG_DIR=$PROJECT/runtime/queue # 日志目录(会自动创建)
STRICT_MATCH=1 # 1=按工作目录严格匹配(推荐),0=只匹配 queue:work
#===================================================
cd "$PROJECT" || exit
# 自动按日期生成日志文件名(格式:queue-20260126.log)
LOG_FILE="$LOG_DIR/queue-$(date +%Y%m%d).log"
# 确保日志目录存在
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
echo "Created log directory: $LOG_DIR"
fi
# 查找并杀掉旧进程(通过工作目录匹配,更精确)
if [ "$STRICT_MATCH" -eq 1 ]; then
# 找到所有 queue:work 进程,然后检查工作目录是否匹配
OLD_PIDS=$(ps -ef | grep '[q]ueue:work' | awk '{print $2}')
if [ -n "$OLD_PIDS" ]; then
for pid in $OLD_PIDS; do
# 获取进程的工作目录
if [ -d "/proc/$pid" ]; then
PWD_PATH=$(readlink /proc/$pid/cwd 2>/dev/null)
if [ "$PWD_PATH" = "$PROJECT" ]; then
echo "Killing old queue worker in $PROJECT (PID $pid) ..."
kill "$pid" 2>/dev/null
fi
fi
done
sleep 2
fi
else
# 简单匹配模式:杀掉所有 queue:work
OLD_PIDS=$(ps -ef | grep '[q]ueue:work' | awk '{print $2}')
if [ -n "$OLD_PIDS" ]; then
echo "Killing old queue workers (PIDs: $OLD_PIDS) ..."
echo "$OLD_PIDS" | xargs kill 2>/dev/null
sleep 2
fi
fi
# 启动指定数量的 worker 进程
echo "Starting $WORKER_NUM queue worker(s) in $PROJECT ..."
for i in $(seq 1 "$WORKER_NUM"); do
nohup "$PHP_BIN" think queue:work --queue "$QUEUE_NAME" --tries "$TRIES" \
>>"$LOG_FILE" 2>&1 &
echo " - Worker #$i started, PID: $!"
done
echo "All workers started successfully!"
echo "Log file: $LOG_FILE"
2) status-queue.sh - 查看队列运行状态(显示队列名、重试次数等)
#!/usr/bin/env bash
#============= 用户配置区(与 start 保持一致) =============
PROJECT=/www/wwwroot/example.com # 项目根目录
STRICT_MATCH=1 # 1=按工作目录严格匹配,0=只匹配 queue:work
#=========================================================
# 查找所有 queue:work 进程
ALL_PIDS=$(ps -ef | grep '[q]ueue:work' | grep -v grep)
if [ -z "$ALL_PIDS" ]; then
echo "Queue worker is NOT running"
exit 0
fi
# 根据匹配模式过滤
MATCHED_COUNT=0
echo "Queue worker status:"
echo "----------------------------------------"
while read -r line; do
PID=$(echo "$line" | awk '{print $2}')
# 严格匹配模式:检查工作目录
if [ "$STRICT_MATCH" -eq 1 ]; then
if [ -d "/proc/$PID" ]; then
PWD_PATH=$(readlink /proc/$PID/cwd 2>/dev/null)
if [ "$PWD_PATH" != "$PROJECT" ]; then
continue # 工作目录不匹配,跳过
fi
else
continue
fi
fi
# 提取队列名称
QUEUE_NAME=$(echo "$line" | grep -oP '(?<=--queue )\S+' || echo "unknown")
# 提取重试次数
TRIES=$(echo "$line" | grep -oP '(?<=--tries )\S+' || echo "N/A")
echo "PID: $PID"
echo " 队列名: $QUEUE_NAME"
echo " 重试次数: $TRIES"
echo " 工作目录: ${PWD_PATH:-$PROJECT}"
echo "----------------------------------------"
((MATCHED_COUNT++))
done <<< "$ALL_PIDS"
if [ $MATCHED_COUNT -gt 0 ]; then
echo "共 $MATCHED_COUNT 个 worker 进程在运行"
else
echo "Queue worker is NOT running in $PROJECT"
fi
3) stop-queue.sh - 停止队列
#!/usr/bin/env bash
#============= 用户配置区(与 start 保持一致) =============
PROJECT=/www/wwwroot/example.com # 项目根目录
STRICT_MATCH=1 # 1=按工作目录严格匹配,0=只匹配 queue:work
#=========================================================
# 查找所有 queue:work 进程
ALL_PIDS=$(ps -ef | grep '[q]ueue:work' | awk '{print $2}')
if [ -z "$ALL_PIDS" ]; then
echo "Queue worker not found, nothing to stop"
exit 0
fi
# 根据匹配模式过滤
MATCHED_PIDS=()
if [ "$STRICT_MATCH" -eq 1 ]; then
# 严格匹配:检查工作目录
for pid in $ALL_PIDS; do
if [ -d "/proc/$pid" ]; then
PWD_PATH=$(readlink /proc/$pid/cwd 2>/dev/null)
if [ "$PWD_PATH" = "$PROJECT" ]; then
MATCHED_PIDS+=("$pid")
fi
fi
done
else
# 简单匹配:所有 queue:work
MATCHED_PIDS=($ALL_PIDS)
fi
if [ ${#MATCHED_PIDS[@]} -gt 0 ]; then
echo "Stopping queue workers in $PROJECT (PIDs: ${MATCHED_PIDS[*]}) ..."
for pid in "${MATCHED_PIDS[@]}"; do
kill "$pid" 2>/dev/null
done
echo "All queue workers stopped (${#MATCHED_PIDS[@]} process(es))"
else
echo "Queue worker not found in $PROJECT, nothing to stop"
fi
使用方法
1) 上传脚本并赋权
chmod +x start-queue.sh status-queue.sh stop-queue.sh
2) 修改三个脚本的"用户配置区"
每个脚本顶部都有 #============= 用户配置区 =============,按你的实际环境修改:
start-queue.sh 配置说明(最重要)
| 变量名 | 说明 | 示例 |
|---|---|---|
PHP_BIN | PHP 可执行文件路径 | /usr/bin/php 或 /www/server/php/81/bin/php |
PROJECT | 项目根目录(必须包含 think 命令) | /www/wwwroot/example.com |
QUEUE_NAME | 要消费的队列名 | default / mail / sms 等 |
TRIES | 任务失败重试次数 | 3(建议 2-5,根据业务容错度调整) |
WORKER_NUM | 同时启动几个 worker 进程 | 1(单机建议 1-4,太多会增加数据库压力) |
LOG_DIR | 日志目录(自动按日期分割) | $PROJECT/runtime/queue(日志文件自动按 queue-20260126.log 格式) |
STRICT_MATCH | 是否按工作目录严格匹配(通过 /proc/$pid/cwd 检查) | 1(推荐,只操作本项目进程)或 0(操作所有 queue:work) |
status-queue.sh 和 stop-queue.sh 配置
只需保证 PROJECT 和 STRICT_MATCH 与 start-queue.sh 一致即可。
3) 启动 / 查看 / 停止
# 启动队列
./start-queue.sh
# 查看状态(会显示队列名、重试次数等详细信息)
./status-queue.sh
# 停止队列
./stop-queue.sh
# 查看今天的日志(实时追踪)
tail -f /www/wwwroot/example.com/runtime/queue/queue-$(date +%Y%m%d).log
# 查看所有日志文件
ls -lh /www/wwwroot/example.com/runtime/queue/
status-queue.sh 输出示例:
Queue worker status:
----------------------------------------
PID: 12345
队列名: default
重试次数: 3
工作目录: /www/wwwroot/example.com
----------------------------------------
PID: 12346
队列名: mail
重试次数: 5
工作目录: /www/wwwroot/example.com
----------------------------------------
共 2 个 worker 进程在运行
核心配置项详解
LOG_DIR:日志自动按日期分割
脚本会自动按日期生成日志文件,格式为 queue-YYYYMMDD.log,所有日志统一放在 LOG_DIR 目录下:
LOG_DIR=$PROJECT/runtime/queue # 配置日志目录
实际效果:
runtime/queue/
├── queue-20260124.log (前天的日志)
├── queue-20260125.log (昨天的日志)
└── queue-20260126.log (今天的日志,正在写入)
优势:
- ✅ 每天自动生成新文件,单个文件不会过大
- ✅ 方便按日期查看和归档
- ✅ 可以轻松实现"只保留最近 N 天"的清理策略
查看今天的日志:
tail -f /www/wwwroot/example.com/runtime/queue/queue-$(date +%Y%m%d).log
TRIES:任务失败重试次数
--tries 3 表示任务执行失败后会再重试 2 次(总共尝试 3 次)。根据你的业务场景调整:
- 幂等任务(如发邮件、推送通知):建议
2-3 - 非幂等任务(如扣款、生成订单):建议
1(失败立即进失败队列,人工介入) - 允许多次重试的任务:可设
5或更高
WORKER_NUM:多 worker 并行消费
设置为 2 或更多时,会同时启动多个进程并行消费队列(提高吞吐量):
WORKER_NUM=4 # 同时启动 4 个 worker
适用场景:
- 队列积压严重,单 worker 消费太慢
- 任务执行时间长(如调用外部 API、文件处理)
注意事项:
- 多 worker 会增加数据库/Redis 连接数,建议结合连接池配置
- 建议不超过 CPU 核心数(一般 2-4 个即可)
- 如果任务有顺序要求,保持
WORKER_NUM=1
STRICT_MATCH:防误杀开关(推荐开启)
当你服务器上有多个 ThinkPHP 项目都在跑队列时:
STRICT_MATCH=1(推荐):只操作"工作目录是当前项目"的进程(安全)STRICT_MATCH=0:操作所有queue:work进程(可能误杀其它项目)
原理:
- 启用后,脚本会检查每个
queue:work进程的工作目录(通过readlink /proc/$pid/cwd) - 只有工作目录 =
$PROJECT的进程才会被操作 - 这比匹配命令行更精确(因为
ps -ef看到的命令行里不一定包含完整项目路径)
为什么不用命令行匹配?
因为启动时已经 cd $PROJECT,执行的是相对路径 think,所以 ps -ef 输出里看不到完整项目路径。使用工作目录匹配更可靠。
原理解释:为什么能"脱离终端"
脚本启动时用的命令是:
nohup php think queue:work --queue default --tries 3 >> queue.log 2>&1 &
nohup:让进程忽略挂断信号(SIGHUP),SSH 断开也不会被杀>> queue.log 2>&1:把标准输出/错误都追加到日志文件&:进程在后台运行,终端立即返回
注意事项与最佳实践
1) 日志自动按日期分割(已内置)
脚本已经内置了按日期分割日志的功能(每天自动生成新文件,格式:queue-20260126.log),避免单个日志文件过大。
查看日志文件列表:
ls -lh /www/wwwroot/example.com/runtime/queue/
# 输出示例:
# queue-20260124.log (3.2M)
# queue-20260125.log (5.1M)
# queue-20260126.log (1.8M)
自动清理 7 天前的旧日志(可加到 crontab):
# 每天凌晨 2 点清理 7 天前的日志
0 2 * * * find /www/wwwroot/example.com/runtime/queue/ -name "queue-*.log" -mtime +7 -delete
手动清理示例:
# 只保留最近 7 天
find /www/wwwroot/example.com/runtime/queue/ -name "queue-*.log" -mtime +7 -delete
# 压缩 3 天前的日志(节省空间)
find /www/wwwroot/example.com/runtime/queue/ -name "queue-*.log" -mtime +3 ! -name "*.gz" -exec gzip {} \;
常见问题
Q1:启动后立即停止 / 查看状态显示未运行?
检查:
PHP_BIN路径是否正确(执行which php或/www/server/php/81/bin/php -v确认)PROJECT路径是否正确(能否cd $PROJECT && ls think)- 查看今天的日志:
tail -50 /www/wwwroot/example.com/runtime/queue/queue-$(date +%Y%m%d).log看报错
Q2:多 worker 会重复消费同一任务吗?
不会。think-queue 底层(基于 Redis/数据库)有锁机制,多个 worker 会自动分配任务,不会重复消费。
Q3:如何监控队列是否还活着?
可以写个定时任务(cron)每 5 分钟执行 status-queue.sh,如果未运行就自动执行 start-queue.sh 或发告警。
Q4:如何查看历史日志或搜索错误?
查看昨天的日志:
tail -100 /www/wwwroot/example.com/runtime/queue/queue-$(date -d "yesterday" +%Y%m%d).log
搜索最近 3 天内的所有错误:
grep -i "error\|exception\|fail" /www/wwwroot/example.com/runtime/queue/queue-*.log | tail -50
查看某个日期的日志:
cat /www/wwwroot/example.com/runtime/queue/queue-20260125.log
统计今天处理了多少任务(假设日志里有 "Processing job" 字样):
grep -c "Processing" /www/wwwroot/example.com/runtime/queue/queue-$(date +%Y%m%d).log