hyperf/swoole常驻进程停机丢任务的主因是未手动注册信号处理器并检查任务状态;必须用pcntl_signal或swoole\process::signal监听sigterm,配合tasking_num、request_count等指标判断任务完成后再退出。

Hyperf、Swoole 或基于 pcntl 的常驻进程,只要在处理异步任务时没做信号拦截和状态检查,停机时就大概率丢任务——不是框架不支持,而是默认不开启。
Hyperf/Swoole 服务收到 SIGTERM 后直接退出?
默认行为就是立刻终止事件循环,swoole_event_exit() 不会等正在运行的协程或 task 完成。必须手动注册信号处理器,并配合任务状态检查逻辑。
- 在
hyperf.php或服务启动前调用pcntl_signal(SIGTERM, $handler),且必须加pcntl_signal_dispatch()触发检查(Swoole 5.0+ 可用Swoole\Process::signal()替代) -
go(function () { while (! $shouldExit) { Coroutine::sleep(0.1); } })这类轮询不能只靠 sleep,要主动检查 task 进程队列长度、协程池是否空闲、HTTP server 是否已拒绝新连接 - 别依赖
isAllRequestsHandled()这种伪函数——Hyperf 没提供现成接口,得自己统计Server::stats()中的tasking_num和request_count差值
Redis 队列消费者进程如何避免任务中断?
用 php artisan queue:work 或自研消费者时,收到信号后若正在执行一个 handle() 方法,直接退出会导致该任务既没成功也没失败,Redis 中的 job 会被标记为 reserved 然后超时释放,可能重复执行。
- 必须在任务开始前设置
set_time_limit(0)并捕获SIGTERM,用全局变量标记“不再取新任务”,但允许当前任务跑完 - Redis 驱动下务必配置
retry_after小于你的最长单任务耗时,否则任务卡住时会被其他 worker 重复取走 - 不要在
handle()里写exit()或die(),它们会绕过 signal handler;改用抛出异常 + try/catch + finally 清理资源
pcntl_fork 子进程场景下,父进程怎么知道子进程真结束了?
只调用 pcntl_wait() 不够——如果子进程被 kill -9 或崩溃,它可能变成僵尸进程,而父进程还在等,导致主流程卡死。
- 必须搭配
pcntl_waitpid($pid, $status, WNOHANG)非阻塞轮询,配合超时计数(比如最多等 30 秒) - 子进程中要显式调用
pcntl_signal(SIGTERM, 'signalHandler'),并在 handler 里exit(0),否则父进程 wait 会永远挂起 - 别忽略
SIGHUP和SIGPIPE,尤其当父进程是 Web 服务器时,子进程 stdout/stderr 断连会触发 SIGPIPE
真正难的不是注册信号,而是判断“任务到底算不算完成”:Redis job 的 reserved_at 时间戳、Swoole task 的 tasking_num、pcntl 子进程的 exit status ——这些状态分散在不同层面,得统一采集、交叉验证,才能说“这次停机没丢任务”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











