Hyperf 3.1 在 Kubernetes 中 SIGTERM 后卡住或队列消息丢失,源于信号注册时机、协程等待逻辑、资源释放顺序未对齐;需确认 WorkerStopHandler 是否启用、register_shutdown_function 是否在 onWorkerStart 中注册,并通过 ProcessStopHandler 或 context 控制队列消费与协程等待。

Hyperf 3.1 服务在 Kubernetes 环境中收到 SIGTERM 后卡住不退出,或异步队列消息被截断丢失,说明优雅退出链路存在断裂——这不是配置没加,而是信号注册时机、协程等待逻辑、资源释放顺序三者没对齐。
确认信号处理器是否真正生效
打开 /config/autoload/signal.php,检查 handlers 数组是否包含 Hyperf\Signal\Handler\WorkerStopHandler::class 且优先级为 PHP_INT_MIN;若被注释或值为 0,则信号根本不会触发停机流程。
运行 php bin/hyperf.php server:monitor,向进程发送 kill -TERM $(pgrep -f "hyperf.php start"),观察控制台是否输出 [INFO] Worker is stopping... —— 没有这行日志,代表信号未被捕获。
注意:register_shutdown_function 必须在 onWorkerStart 回调中注册,全局写一次等于没写,每个 Worker 是独立进程,不继承父进程钩子。
让异步队列消费完成再退出
方法一:启用 ProcessStopHandler(推荐)
修改 /config/autoload/signal.php,将 Hyperf\Process\Handler\ProcessStopHandler::class 加入 handlers 数组,并确保 timeout ≥ 队列单条消息最大处理时长。
方法二:手动控制消费开关
在信号处理器中设置全局标志位 $this->shouldStop = true,并在队列消费者循环开头添加 if ($this->shouldStop) break;,确保本次消息处理完即退出。
方法三:用 context 控制生命周期
为每条消息生成独立 context.WithTimeout,超时自动取消;避免 sleep(30) 这类无上下文阻塞调用,否则 WaitGroup 永远等不到它结束。
强制等待活跃协程与连接关闭
第一步:监听 SIGTERM 并标记退出状态
在 /app/Bootstrap/GracefulShutdown.php 中注册信号:pcntl_signal(SIGTERM, [$this, 'onSigterm']);
第二步:关闭新请求入口,但保留旧连接
调用 $server->shutdown(); 停止 accept,但不关闭已建立的 TCP 连接;此时 Ingress 已摘除端点,新流量不再进来。
第三步:等待所有协程自然结束
使用 go(function () { while (Co::stats()['coroutine_num'] > 0) { Co::sleep(0.1); } }); 循环检测协程数,直到归零。注意:不能直接用 swoole_event_exit(),否则正在执行的协程会被硬杀。
第四步:释放数据库连接池与 Redis 客户端
显式调用 Db::close() 和 RedisFactory::get()->close(),否则连接会滞留至进程终止才由内核回收,造成连接泄漏。


















