Hyperf 中信号不直接关闭 MySQL 连接,而是通过 WorkerStopHandler 设置 worker.stopping 标志,触发 GracefulStopMiddleware 拒绝新请求、连接池在 release 时主动销毁空闲连接,并依赖 max_idle_time、wait_timeout 和 ATTR_TIMEOUT 协同保障安全退出。

Hyperf 里没有“协程 MySQL 信号触发数据库关闭”这回事——信号(SIGTERM/SIGINT)只作用于进程,不直接通知连接池或 PDO 实例;所谓“安全关闭”,其实是靠信号 handler 设置标记 + 连接池主动配合释放空闲连接来完成的。
为什么不能靠信号直接关 MySQL 连接
MySQL 连接是 PHP 层的资源(PDO 或 mysqli 对象),而 SIGTERM 是操作系统发给整个 Worker 进程的。Swoole 的 Worker 进程在事件循环中阻塞运行,不会自动遍历并销毁所有 PDO 实例。如果你只注册了 pcntl_signal(SIGTERM, fn() => exit()),那连接会随进程终止被内核回收,但:
- 没机会执行
$pdo->close()或置空释放,可能丢掉最后的事务提交或未 flush 的日志 - 连接池里的空闲连接不会被显式
destroy(),DB 端仍维持 Sleep 状态几秒甚至更久 - 正在执行的协程查询会被强制中断,MySQL 可能留下未清理的锁或临时表
Hyperf 真正的安全关闭链条
Hyperf 的 WorkerStopHandler 并不是去“关 MySQL”,而是启动一套协同退出流程:
- 收到
SIGTERM后,调用Context::set('worker.stopping', true) - 后续所有新 HTTP 请求进入
GracefulStopMiddleware,直接返回 503,不再分发到路由 - 已建立的长连接(如 WebSocket、SSE)允许自然完成,但不再 accept 新连接
- 连接池在每次
release()时检查worker.stopping,若为 true,则不复用该连接,而是调用destroy()彻底释放 - 空闲连接受
pool.max_idle_time约束,超时后也会被销毁(注意:仅对hyperf/database的 pdo/mysqli 驱动生效)
你必须手动配的三个关键点
默认配置下,MySQL 连接不会“感知”停机,必须显式对齐:
-
pool.max_idle_time设为 30(单位:秒),写在config/autoload/db.php的对应数据库pool下,不能写错层级 -
pool.wait_timeout建议设为 5.0,避免协程卡在等连接上,拖慢整体退出节奏 - 确保
options.ATTR_TIMEOUT(PDO 执行超时)设为 3,防止某条慢查询阻塞整个 Worker 退出
这三个值要形成闭环:max_idle_time 控制空闲连接寿命,wait_timeout 控制排队协程耐心,ATTR_TIMEOUT 控制单次查询底线。
最容易被忽略的 Redis 和自定义 Process
Hyperf 的 WorkerStopHandler 不管 Redis 连接池和自定义 Process:
-
hyperf/redis完全不认max_idle_time,得改用pool.idle_timeout(注意命名差异) - 自定义
Process(比如定时任务、消息监听)必须手动加ProcessStopHandler到config/autoload/signal.php,否则它会一直跑,直到被 OS 强杀 - Kubernetes 场景下,
preStop里别写kill -SIGTERM $PID—— Hyperf 已监听该信号,重复发送会导致worker.stopping被重置或跳过清理逻辑
连接池是否真正清空,不能只看配置文件有没有写,得查 hyperf:pool-status 输出里的 idle_num 是否归零,以及 DB 端 SHOW PROCESSLIST 中 Sleep 连接是否在 max_idle_time 后消失。



















