- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
导读
Hyperf 的hyperf/signal组件提供了一套基于协程的信号监听机制,它会在Worker进程和自定义(custom)进程启动后自动向信号管理器注册处理器,让开发者能够以统一的SignalHandlerInterface接口捕获SIGTERM、SIGINT等系统信号并执行自定义逻辑。本文以 docs/en/signal.md 为骨架,结合 signal 组件源码 与测试用例,完整讲解组件的安装配置、自定义处理器编写、优先级机制、优雅停机方案,以及协程风格服务下的监听器适配,帮助你在 Hyperf 项目中可靠地管理进程生命周期。
一、Signal Handler 是什么
在常驻内存的服务端程序中,进程生命周期管理依赖操作系统信号(Signal),例如:
SIGTERM(默认信号 15):请求进程终止,是kill命令、Docker Stop 等场景最常触发的信号;SIGINT(信号 2):终端键盘Ctrl + C产生的中断信号。
Hyperf 的 Signal 组件把“某个进程类型监听到某个信号后做什么”抽象为一个个Handler(信号处理器)。它的核心能力包括:
- 自动注册:信号处理器会在
Worker进程和自定义进程启动后,自动注册到SignalManager(见 SignalRegisterListener),无需手动初始化; - 协程化等待:监听循环运行在协程中,通过
EngineSignal::wait()异步等待信号,不阻塞进程主逻辑(见 SignalManager::listen()); - 优雅退出:提供开箱即用的
WorkerStopHandler,让收到SIGTERM/SIGINT的 Worker 进程在等待业务处理完成后平滑退出。
二、安装与发布配置
1. 安装组件
composer require hyperf/signal组件包hyperf/signal要求php >= 8.2,并依赖hyperf/contract、hyperf/coordinator、hyperf/coroutine、hyperf/engine、hyperf/stdlib、hyperf/support等 Hyperf 3.2 系列组件(见 src/signal/composer.json)。
2. 发布默认配置文件
php bin/hyperf.php vendor:publish hyperf/signal发布命令会在项目中生成config/autoload/signal.php,其默认内容与仓库内置模板一致(见 src/signal/publish/signal.php):
<?php declare(strict_types=1); return [ 'handlers' => [ // Hyperf\Signal\Handler\WorkerStopHandler::class => PHP_INT_MIN ], 'timeout' => 5.0, ];两个关键配置项的含义:
| 配置项 | 默认值 | 说明 |
|---|---|---|
handlers | [] | 以「处理器类名 => 优先级」形式注册的处理器映射;若只写类名而不写优先级,源码中会被当作0处理(见 SignalManager::getQueue()) |
timeout | 5.0 | 信号等待超时时间(秒),即每次EngineSignal::wait()的超时上限,同时是SignalManager读取配置signal.timeout时的兜底默认值 |
注意:即使不发布配置文件,
SignalManager也会通过$this->config->get('signal.timeout', 5.0)使用内置的5.0秒默认超时(见 SignalManager.php),因此该配置文件是可选的。
三、编写自定义 Handler
1. 核心接口SignalHandlerInterface
所有信号处理器都必须实现 SignalHandlerInterface,该接口定义了两种进程类型常量与两个方法:
interface SignalHandlerInterface { public const WORKER = 1; // Worker 进程 public const PROCESS = 2; // 自定义进程 /** @return array [[ WORKER, SIGNAL ]] */ public function listen(): array; public function handle(int $signal): void; }listen():声明要监听的「进程类型 + 信号」组合,返回形如[[WORKER, SIGTERM], [PROCESS, SIGUSR1]]的二维数组;handle(int $signal):当对应信号被捕获后回调,参数为实际收到的信号值。
2. 注册方式一:#[Signal]注解
在处理器类上添加#[Signal]注解即可被自动扫描注册。注解本身只有一个可选参数priority(见 src/signal/src/Annotation/Signal.php):
#[Attribute(Attribute::TARGET_CLASS)] class Signal extends AbstractAnnotation { public function __construct(public ?int $priority = null) { } }注册时,SignalManager::init()会通过AnnotationCollector::getClassesByAnnotation(Signal::class)收集所有带注解的类,并把注解上的priority ?? 0作为优先级插入队列(见 SignalManager::getQueue())。
3. 注册方式二:配置文件handlers
将处理器类名写入config/autoload/signal.php的handlers数组即可,同样可指定优先级:
return [ 'handlers' => [ App\Signal\TermSignalHandler::class => 100, ], 'timeout' => 5.0, ];4. 完整示例:监听 Worker 进程的 SIGTERM
下面监听Worker进程的SIGTERM信号,并在收到信号时打印信号值(原文示例):
<?php declare(strict_types=1); namespace App\Signal; use Hyperf\Signal\Annotation\Signal; use Hyperf\Signal\SignalHandlerInterface; #[Signal] class TermSignalHandler implements SignalHandlerInterface { public function listen(): array { return [ [SignalHandlerInterface::WORKER, SIGTERM], ]; } public function handle(int $signal): void { var_dump($signal); } }从源码角度看,该处理器被SignalManager::init()解析后,会落入handlers[WORKER][SIGTERM]分组(见 SignalManager::init()),随后listen(WORKER)会为SIGTERM单独创建一个协程持续等待信号。
5. 优先级如何生效
SignalManager使用SplPriorityQueue管理所有处理器,数值越大越先执行:
- 配置中的处理器按
handler => priority插入; - 注解处理器按
priority ?? 0插入; - 未声明优先级的类默认按
0处理。
测试用例 SignalManagerTest 验证了这一点:当SignalHandler2Stub::class => 1、SignalHandlerStub::class(优先级 0)同时注册时,getHandlers()[WORKER][SIGTERM][0]是优先级更高的SignalHandler2Stub实例。因此,把WorkerStopHandler配置为PHP_INT_MIN意味着它拥有最低优先级,保证其它业务处理器先完成清理,最后才执行进程停止逻辑。
四、优雅停机:WorkerStopHandler 详解
捕获SIGTERM后,如果没有任何“停止”处理器,Worker 进程会被信号直接打断,业务可能来不及收尾;而一旦被TermSignalHandler这类处理器捕获,进程又无法自行正常退出(这正是文档中“捕获后无法正常退出”的原因)。
Hyperf 提供了内置的 WorkerStopHandler 来解决该问题,其完整实现:
namespace Hyperf\Signal\Handler; use Hyperf\Contract\ConfigInterface; use Hyperf\Signal\SignalHandlerInterface; use Psr\Container\ContainerInterface; use Swoole\Server; class WorkerStopHandler implements SignalHandlerInterface { protected ConfigInterface $config; public function __construct(protected ContainerInterface $container) { $this->config = $container->get(ConfigInterface::class); } public function listen(): array { return [ [self::WORKER, SIGTERM], [self::WORKER, SIGINT], ]; } public function handle(int $signal): void { if ($signal !== SIGINT) { $time = $this->config->get('server.settings.max_wait_time', 3); sleep($time); } $this->container->get(Server::class)->stop(); } }启用方式(在config/autoload/signal.php中注册,原文配置):
<?php declare(strict_types=1); return [ 'handlers' => [ Hyperf\Signal\Handler\WorkerStopHandler::class => PHP_INT_MIN ], 'timeout' => 5.0, ];其工作流程可以概括为:
- 监听:同时监听 Worker 进程的
SIGTERM与SIGINT; - 等待收尾:收到非
SIGINT信号(即SIGTERM)时,先sleep()等待server.settings.max_wait_time(默认3秒)——该配置即 Swoole Server 设置中的max_wait_time,用于给正在处理的请求/任务留出完成时间;收到SIGINT(Ctrl + C)时则跳过等待直接停止; - 停止服务:通过容器取出
Swoole\Server实例并调用stop(),触发当前进程平滑关闭。
因此,在生产环境(如 Docker/K8s 下发SIGTERM)中,注册WorkerStopHandler后即可实现“请求处理完毕再退出”的优雅停机;在本地调试时,也可以直接用Ctrl + C(SIGINT)退出。
注意:
WorkerStopHandler面向异步风格服务(Swoole Server 模型)。它不适合协程风格(Coroutine Server)服务,协程场景需要自行实现或使用下文的自定义方案。
五、协程风格服务下的信号监听配置
Hyperf 支持「异步风格」与「协程风格」两种服务模型。由于协程风格服务在单 Worker 内通过协程调度,WorkerStopHandler所依赖的Swoole\Server::stop()语义并不适用,需要自定义处理器。仓库为此提供了 CoroutineServerStopHandler:
class CoroutineServerStopHandler implements SignalHandlerInterface { public function listen(): array { return [ [self::WORKER, SIGTERM], [self::WORKER, SIGINT], ]; } public function handle(int $signal): void { ProcessManager::setRunning(false); CoordinatorManager::until(Constants::WORKER_EXIT)->resume(); } }它通过ProcessManager::setRunning(false)通知进程管理器停止运行,并通过CoordinatorManager唤醒等待WORKER_EXIT的协程,驱动协程风格服务正常收尾。
文档中的自定义实现示例
原文还给出了一种基于ServerManager的自定义协程风格停止处理器(适合需要显式关闭所有已监听服务的情形),可直接放在App\Kernel\Signal下:
<?php declare(strict_types=1); namespace App\Kernel\Signal; use Hyperf\Contract\ConfigInterface; use Hyperf\Process\ProcessManager; use Hyperf\Server\ServerManager; use Hyperf\Signal\SignalHandlerInterface; use Psr\Container\ContainerInterface; class CoroutineServerStopHandler implements SignalHandlerInterface { protected ContainerInterface $container; protected ConfigInterface $config; public function __construct(ContainerInterface $container) { $this->container = $container; $this->config = $container->get(ConfigInterface::class); } public function listen(): array { // There is only one Worker process in the coroutine style, so you only need to monitor the WORKER here. return [ [self::WORKER, SIGTERM], [self::WORKER, SIGINT], ]; } public function handle(int $signal): void { ProcessManager::setRunning(false); foreach (ServerManager::list() as [$type, $server]) { // Cyclically close open services $server->shutdown(); } } }要点解读:
- 协程风格服务只有一个 Worker 进程,因此
listen()只需监听WORKER即可; handle()首先ProcessManager::setRunning(false)让常驻协程/自定义进程感知停止意图;- 随后遍历
ServerManager::list()中注册的每一个服务(返回[$type, $server]元组),逐一调用$server->shutdown()关闭服务端口与连接,实现平滑下线。
将该类注册进config/autoload/signal.php的handlers即可在协程风格服务中启用。
六、信号注册与注销的生命周期
信号监听并非全进程范围内“一刀切”,而是跟随进程事件精确注册与注销:
注册时机:
SignalRegisterListener监听三个事件(见 src/signal/src/Listener/SignalRegisterListener.php):BeforeWorkerStart:Worker 进程启动前,注册WORKER组监听;BeforeProcessHandle:自定义进程处理前,注册PROCESS组监听;MainCoroutineServerStart:主协程服务启动时,注册WORKER组监听。
每个监听循环都是一个独立协程(
Coroutine::create),内部while (true)反复调用EngineSignal::wait($signal, timeout),收到信号后按序触发该信号下的全部处理器,直至进程退出标记被置为stopped(见 SignalManager::listen())。注销时机:
SignalDeregisterListener监听OnWorkerExit、AfterProcessHandle、CoroutineServerStop、AllCoroutineServersClosed,通过SignalManager::setStopped(true)终止监听循环(见 src/signal/src/Listener/SignalDeregisterListener.php)。
这条“随事件注册、随事件注销”的链路,保证了监听循环不会在进程退出后残留协程,也不会在自定义进程中误监听本应属于 Worker 的信号。
七、验证与测试
组件自带测试 SignalManagerTest,可以直观验证处理器注册与优先级排序行为:
$manager = new SignalManager($container); $manager->init(); $this->assertArrayHasKey(SignalHandler::WORKER, $manager->getHandlers()); $this->assertArrayHasKey(SIGTERM, $manager->getHandlers()[SignalHandler::WORKER]); $this->assertInstanceOf(SignalHandler2Stub::class, $manager->getHandlers()[SignalHandler::WORKER][SIGTERM][0]); $this->assertInstanceOf(SignalHandlerStub::class, $manager->getHandlers()[SignalHandler::WORKER][SIGTERM][1]);该用例使用 Mock 容器注册了两个监听WORKER + SIGTERM的处理器,其中SignalHandler2Stub优先级为1、SignalHandlerStub为0。断言结果[0]是SignalHandler2Stub、[1]是SignalHandlerStub,印证了SplPriorityQueue数值越大越先执行的排序规则——这也是配置WorkerStopHandler为PHP_INT_MIN能够“最后兜底”的原因所在。
八、小结
通过hyperf/signal组件,Hyperf 把进程信号管理收敛为清晰的 Handler 模型:
- 实现
SignalHandlerInterface,用listen()声明「进程类型 + 信号」、用handle()定义响应逻辑; - 通过
#[Signal]注解或config/autoload/signal.php的handlers数组注册,并可借助优先级控制执行顺序; - 异步风格服务启用内置
WorkerStopHandler(优先级PHP_INT_MIN)实现优雅停机,并依赖server.settings.max_wait_time控制等待时长; - 协程风格服务需使用
CoroutineServerStopHandler或文档给出的ServerManager自定义方案; - 整个监听生命周期由
SignalRegisterListener/SignalDeregisterListener按进程事件自动管理,监听循环以协程方式异步运行于SignalManager中。
相关参考资源:组件源码 src/signal/src、默认配置模板 src/signal/publish/signal.php、测试用例 src/signal/tests/SignalManagerTest.php、原始文档 docs/en/signal.md。
- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Hyperf Signal 信号处理组件实战指南:自定义信号监听与优雅停机
Hyperf Signal 信号处理组件实战指南:自定义信号监听与优雅停机 Hyperf 的 hyperf/signal 组件为常驻内存的 Swoole 服务提
后端微服务Hyperf 信号处理(Signal)组件完全指南:优雅处理 Worker 与自定义进程的进程信号
Hyperf 信号处理(Signal)组件完全指南:优雅处理 Worker 与自定义进程的进程信号 Hyperf 的 hyperf/signal 组件为 Swo
后端Web框架微服务RPC框架异步编程Hyperf框架中自定义进程监听Term信号的最佳实践
Hyperf框架中自定义进程监听Term信号的最佳实践 引言:为什么需要优雅地处理Term信号? 在Hyperf框架的微服务架构中,自定义进程(Custom P
后端Web框架微服务RPC框架异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考