hyperf默认不处理sigterm,因其基于swoole协程运行,pcntl_signal()需配合ticks或主动调度才生效,且容器中若用shell启动(pid=1为shell),信号会被拦截丢弃,无法抵达php进程。

Hyperf 微服务必须手动监听 SIGTERM 才能实现优雅停机;默认不响应,直接 kill 会导致请求中断、连接泄漏、数据不一致。
为什么 Hyperf 默认不处理 SIGTERM?
Hyperf 基于 Swoole 协程运行,主进程不是传统阻塞式模型,pcntl_signal() 不会自动触发协程调度,且容器中若用 shell 启动(如 sh -c "php bin/hyperf.php start"),pid=1 是 shell 进程——它既不转发信号,也不做清理,SIGTERM 根本到不了 PHP 层。
- 现象:执行
docker stop或 Kubernetes 发送SIGTERM后,容器立即退出,日志里看不到任何 shutdown 日志 - 根本原因:信号被 shell 拦截丢弃,或 PHP 未注册处理器,或注册了但没触发协程调度
- 验证方式:在容器内执行
ps aux | grep php,确认 pid=1 是否为php进程;再用kill -TERM 1测试是否触发自定义逻辑
pcntl_signal() 必须配合 pcntl_signal_dispatch() 或 ticks
仅调用 pcntl_signal() 注册回调不够——Swoole 的事件循环会阻塞 PHP 的信号检查。必须启用 ticks 或在协程中主动调度信号。
- 推荐写法(放在
bin/hyperf.php启动入口最上方):
declare(ticks = 1);
pcntl_signal(SIGTERM, function () {
echo "SIGTERM received, starting graceful shutdown...\n";
$server = \Hyperf\Server\ServerFactory::getServer();
if ($server->getServer() instanceof \Swoole\Http\Server) {
$server->getServer()->shutdown();
}
exit(0);
});
- 关键点:
declare(ticks = 1)让 PHP 解释器每执行一条语句就检查一次信号队列;没有它,回调永远不会执行 - 不要用
pcntl_signal_dispatch()手动轮询——Swoole 长时间阻塞时它可能根本没机会被调用 - 避免在回调里做耗时操作(如 DB 查询、HTTP 调用),只触发 shutdown,让 Swoole 自身完成连接排空
Docker 和 Kubernetes 环境下必须配对设置
光有 PHP 层信号处理还不够,容器编排层必须给足时间、正确发信号、并配合流量摘除。
- Docker Compose:在
docker-compose.yml中设stop_signal: SIGTERM和stop_grace_period: 30s,否则默认发SIGKILL - Kubernetes:Deployment 必须配置
terminationGracePeriodSeconds: 45(建议 ≥ PHP 层最大等待时间),且容器镜像STOPSIGNAL SIGTERM - 务必禁用 shell 启动:Dockerfile 中用
exec php bin/hyperf.php start替代sh -c "php bin/hyperf.php start",确保 PHP 进程是 pid=1 - 健康检查探针要配合:livenessProbe 失败后不应立刻删 Pod,readinessProbe 应在 shutdown 开始前就返回失败,让流量提前摘除
最容易被忽略的是 shell 启动导致信号丢失——90% 的“优雅停机失效”问题都卡在这一步。哪怕 PHP 代码写得再完善,信号到不了进程,一切归零。











