不能直接用 pool->shutdown() 动态缩容,因其会强制终止所有 worker 导致任务丢失;需通过 SIGUSR2 信号实现优雅驱逐:空闲 worker 主动退出,忙碌者完成后再退,并配合状态轮询与信号标志位控制。

为什么不能直接用 pool->shutdown() 来动态缩容
PHP 工作池的动态缩容不是简单调用关闭方法就能完成的——pool->shutdown() 会终止所有 worker 进程,包括正在处理任务的,导致任务丢失或 SIGKILL 强杀。真正需要的是「优雅驱逐」:让空闲 worker 主动退出,正在忙的继续跑完再退。主流 Composer 库如 php-pm、amphp/cluster 或轻量级 spiral/roadrunner(配合 PHP-PM 插件)都不原生暴露“降 Worker 数”接口,必须靠信号 + 状态轮询 + 生命周期控制来实现。
用 SIGUSR2 触发 worker 主动退出的实操路径
这是最可靠、被 php-pm 和定制 pool 实现广泛采用的方式。核心逻辑是:主进程向指定 worker 发送 SIGUSR2,worker 捕获后检查自身是否空闲(无 pending task、无 running coroutine),满足则 clean exit;否则忽略,等下次轮询再试。
- 主进程需维护每个 worker 的 PID 和状态映射(可用
pcntl_fork()后记录,或用 Redis 做共享状态) - worker 内必须注册信号处理器:
pcntl_signal(SIGUSR2, fn() => $this->shouldExit = true),且在每次任务循环前检查$shouldExit - 不要在 signal handler 里做耗时操作(如 DB 查询、文件写入),只设标志位
-
pcntl_signal_dispatch()必须在事件循环中周期调用(例如 Amp 的Loop::tick()或 Swoole 的eventloop中)
amphp/parallel 不适合动态缩容的三个硬伤
虽然 amphp/parallel 提供了 WorkerPool 类,但它设计目标是「短生命周期任务调度」,而非长驻型工作池。实际用起来会踩坑:
- worker 进程启动后无法被外部控制生命周期——没有暴露
killWorker(int $id)或类似方法 - 所有 worker 共享同一套启动参数,缩容只能靠整体重建
WorkerPool实例,导致正在执行的任务被中断 - 底层基于
proc_open启动子进程,不支持 Unix socket 通信或共享内存,无法实时同步 worker 负载状态
想用它做动态缩容,得自己封装一层进程管理器 + 信号桥接层,成本远高于换用 spiral/roadrunner 或手写基于 pcntl 的 pool。
立即学习“PHP免费学习笔记(深入)”;
缩容阈值判断该看 CPU 还是队列长度
看场景。HTTP API 类服务建议以「就绪队列长度」为主指标:queue_length / worker_count 持续 30 秒触发缩容;而 CPU 密集型批处理任务(如图像转码)才应参考 <code>sys_getloadavg()[0]。注意两点:
- 不要依赖
memory_get_usage()——PHP 内存回收不可控,worker 即使空闲也可能暂未释放内存 - 避免每秒轮询,改用指数退避:首次检测间隔 1s,之后每次失败+500ms,直到成功或超时
- 缩容操作本身要加锁(例如用
flock()锁文件),防止多个监控进程同时发起缩容请求
真正难的不是代码怎么写,而是缩容节奏和业务 SLA 的平衡——缩太急,突发流量打满新队列;缩太慢,资源闲置成本上升。这个阈值得在压测环境里反复调。



















