hyperf必须用pcntl_signal而非轮询标志,因为swoole worker进程为阻塞式事件循环,仅靠sleep+标志检查会导致信号被忽略,停机延迟甚至超时;pcntl_signal配合declare(ticks=1)或pcntl_signal_dispatch()才能确保信号即时捕获。

Hyperf 为什么必须用 pcntl_signal 而不是简单 sleep + 检查标志?
因为 Swoole 的 Worker 进程默认是阻塞式事件循环,不主动让出控制权。如果只靠轮询 $shouldExit 标志,信号可能在 sleep() 或长 IO 中被完全忽略,直到下一次循环 —— 这会导致停机延迟甚至超时失败。
Hyperf 底层依赖 pcntl_signal 注册处理器,并配合 declare(ticks=1) 或 pcntl_signal_dispatch() 主动触发信号分发。这是唯一能确保信号“即时被捕获”的方式。
-
declare(ticks=1)必须放在脚本最顶部,且仅对同步代码生效;异步协程中需显式调用pcntl_signal_dispatch() - Hyperf 的
WorkerStopHandler类本质就是封装了这个逻辑,自动注册SIGTERM/SIGINT并设置上下文标记worker.stopping - 若手动写信号处理,切忌在 handler 里做耗时操作(如 DB 写入),只设标志、记录日志即可
Context::set('worker.stopping', true) 到底影响什么?
这不是一个魔法开关,而是 Hyperf 路由中间件和生命周期钩子的判断依据。一旦该上下文值为 true,后续请求会进入“拒绝新流量”阶段:
-
GracefulStopMiddleware开始返回 503,不再转发新请求 - HTTP Server 不再 accept 新连接,但已建立的连接仍可完成响应
- 异步队列消费者(
AsyncQueueMessage)会在当前消息处理完后退出,不会中断正在执行的sleep()或 DB 操作 - 自定义
Process需要自己检查该上下文,否则无法感知停止信号
为什么 ProcessStopHandler 和 WorkerStopHandler 要分开配置?
Worker 进程和自定义进程(如定时任务、消息监听)生命周期不同、资源依赖不同。混用同一套停止逻辑容易导致:
- Worker 已退出,但自定义进程还在跑,继续消费消息造成重复或状态错乱
- 自定义进程先停,Worker 却还在处理请求,而依赖的服务(如 Redis 连接池)已被释放
- Hyperf 默认只注册
WorkerStopHandler,ProcessStopHandler需手动加到config/autoload/signal.php的handlers数组里 - 两者 timeout 可不同:
Worker宽限期通常 5–10s,Process可设为 30s 以应对长周期任务
Kubernetes 下 preStop 和 Hyperf 信号处理怎么配合才不打架?
常见错误是 preStop 里执行 kill -SIGTERM $PID,结果触发两次 SIGTERM:一次来自 K8s,一次来自 preStop 命令本身。这会让 Hyperf 认为信号重复,可能提前终止或忽略第二次。
正确做法是让 K8s 直接发信号,不干预进程内部逻辑:
-
terminationGracePeriodSeconds必须 ≥ Hyperf 的timeout(默认 5.0s),建议设为 15–30s 留缓冲 -
preStop仅用于等待或健康检查,比如curl -f http://localhost:9501/healthz,而不是发 kill - 确认容器
STOPSIGNAL SIGTERM(Dockerfile 中),避免 K8s 默认用SIGKILL - Hyperf 日志里看到
"收到停止信号,准备优雅退出"就说明信号链路通了;没看到则大概率是信号被屏蔽或未注册
真正难的不是写几行信号注册代码,而是让每个进程类型、每种资源依赖、每层流量入口都对同一个停止信号做出一致且有序的响应 —— 这需要逐层验证,不能只信文档。











