1. 背景说明
在生产环境中,通过 Supervisor 管理 Swoole 服务是常见做法。然而,在执行 supervisorctl restart 或 stop → start 时,开发者常遇到以下问题:
- 子进程(Swoole worker)无法被正常杀死
- 重启后依旧存在残留 worker
- 强制 kill -9 也无效(可能处于 IO 阻塞)
- 重启过程缓慢导致服务无法即时恢复
本文从 Swoole 进程模型、信号处理机制、Supervisor 行为、业务阻塞问题 等角度进行技术分析,并提供最佳实践配置与建议。
2. Swoole 进程模型概述
Swoole Server(如 HTTP / WebSocket / TCP)的基本进程结构:
Master Process
│
├── Manager Process
│
└── Worker Processes (N 个)关键特点:
- Worker 是独立子进程,由 Swoole 的 Master 和 Manager 管理
- Worker 在处理请求时必须“优雅退出”,不能强行中断
- Worker 使用事件循环 + 协程执行任务
Worker 退出前会尝试等待:
- 当前请求处理完成
- 当前协程执行完
- 当前 IO 任务结束
因此,如果 Worker 正在执行耗时任务或出现 IO 阻塞,就会:
✔ 不立即退出
✔ 收到信号后延迟退出
✔ 占用进程表造成“杀不死”现象
3. Supervisor 的进程管理方式
Supervisor 在停止进程时的默认流程:
- 发送
SIGTERM - 等待
stopwaitsecs指定时间 - 若进程未退出 → 发送
SIGKILL (kill -9) - 若仍未退出 → 进程处于不可杀状态(D 状态),无法强制终止
Supervisor 与 Swoole 的行为差异导致了“不易杀死”的问题:
| 项目 | Swoole Worker 行为 | Supervisor 行为 |
|---|---|---|
| SIGTERM | 优雅退出,等待任务 | 尝试快速终止 |
| 任务未完成 | 等待完成 | 超时后强制 kill |
| IO 阻塞 | 无法退出 | kill 无效 |
4. 子进程“杀不死”的技术原因分析
4.1 Worker 正在处理未完成的业务
包括:
- 长时间业务逻辑
- 无限循环未 yield
- 大量同步阻塞 IO(MySQL、Redis、文件)
- sleep、usleep 等阻塞函数
- 繁忙协程无法调度
Swoole 在任务未完成前无法退出,因此 ignore SIGTERM。
4.2 Worker 处于不可杀状态(D 状态)
通过 ps aux 可看到 D:
STAT = D (Uninterruptible sleep)此状态下:
- kill -9 无效
- Supervisor 无法结束进程
- 通常是 IO 阻塞导致(NFS、磁盘坏块、Redis / MySQL hang 等)
4.3 Swoole 未关闭 daemonize
如果配置:
'daemonize' => 1则:
- Swoole 会从 Supervisor 分离
- Worker 不在同一进程组
- Supervisor 无法管理子进程
导致大量残留 zombie 或 orphan。
5. 最佳实践配置(Supervisor + Swoole)
5.1 Supervisor 配置示例
[program:app]
command=php /Service/script/bin/server.php start --conf-SService
autostart=true
autorestart=true
stdout_logfile=/service/SService/app.out
stopasgroup=true
killasgroup=true
stopsignal=TERM
stopwaitsecs=10说明:
stopasgroup=true:停止时给整个进程组发信号killasgroup=true:确保 worker 一并被 killstopsignal=TERM:适用于 Swoole 优雅退出stopwaitsecs=10:给 Swoole 10 秒处理未完成任务
5.2 Swoole 配置示例(关键)
$server->set([
'daemonize' => 0,
'max_wait_time' => 3, // 优雅退出最多允许 3 秒等待
'log_file' => '/opt/log/swoole.log',
]);max_wait_time 是最关键参数
收到 SIGTERM 后,Worker 至多等待 X 秒,否则强制退出。
目的:
- 避免 Worker 长期因业务阻塞而无法退出
- 避免 Supervisor restart 卡住
- 确保服务可平滑更新
6. 如何确认 Worker 是否阻塞
方法一:查看 worker 状态
ps -o pid,ppid,stat,cmd -p <pid>D→ IO 阻塞S→ 正在等待退出R→ 忙(正在执行)
方法二:strace 调用观察
strace -p <pid>如果看到:
read(...)accept(...)recvfrom(...)- 卡在 Redis, MySQL 等 IO 调用
说明 worker 正被阻塞。
7. 总结
如果你遇到:
- Supervisor restart 时 Swoole worker “杀不死”
- worker 残留、僵尸进程、卡住无法退出
- kill -9 无效
那么原因基本是:
- worker 正在处理未完成的业务
- worker 有同步阻塞 IO
- daemonize = 1 导致进程脱离 Supervisor
- 没有设置 max_wait_time 导致优雅退出卡死
解决方案:
- 关闭
daemonize - 设置
max_wait_time - 使用
killasgroup / stopasgroup - 避免阻塞业务
8. 附:推荐的完整 Swoole + Supervisor 环境模板
server.php(核心配置)
$server->set([
'worker_num' => 4,
'daemonize' => 0,
'max_wait_time' => 3,
'log_file' => '/opt/log/swoole.log',
]);Supervisor 配置
[program:app]
command=php /Service/script/bin/server.php start
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopsignal=TERM
stopwaitsecs=10
stdout_logfile=/opt/log/app.out