本文目录导读:

- 核心原理
- 方法一:发送信号(最常用、最标准)
- 方法二:使用
reload()方法(编程式,推荐) - 方法三:使用 Swoole 官方管理工具(如 Swoole 管理面板或 Supervisord 配合)
- 关键配置参数(提高平滑重启的可靠性)
- 常见问题与坑
- 总结最佳实践
在 PHP Swoole 项目中,平滑重启(也称为“无缝重启”或“热重启”)是指在不中断当前正在处理的请求的情况下,优雅地停止旧的 Worker 进程,并启动新的 Worker 进程来加载更新后的代码。
Swoole 主要通过两种方式实现平滑重启:信号机制 和 Reload 机制。
核心原理
- Manager 进程:负责管理 Worker 进程。
- Worker 进程:处理具体的业务逻辑。
- 重启流程:Master 进程收到重启信号 -> 通知 Manager -> Manager 向旧的 Worker 发送
SIGTERM信号,等待其处理完当前请求后退出 -> 同时创建新的 Worker 进程加载新代码。
发送信号(最常用、最标准)
这是 Swoole 官方推荐的方式,通过向 Master 进程发送特定的信号来实现。
-
找到 Master 进程 PID 通常在
Server::start()时会写入 PID 文件,或者通过ps aux | grep [your_server_name]查找。 -
发送信号
SIGUSR1:平滑重启所有 Worker 进程,这是最常用的操作。SIGUSR2:平滑重启所有 Task Worker 进程(如果启用了 Task 功能)。SIGTERM:强制终止,不等待请求处理完毕(不推荐用于平滑重启)。
命令行示例:
# 假设你的服务主进程 PID 是 1234 # 平滑重启所有 Worker 进程 kill -USR1 1234 # 或者从 PID 文件读取 kill -USR1 $(cat /var/run/your_app.pid)
优点:简单、直接、无依赖。 缺点:需要记住信号指令,且不会触发自定义的回调逻辑。
使用 reload() 方法(编程式,推荐)
在 Swoole 4.5+ 版本中,推荐使用 Server 对象的 reload() 方法,这比直接发信号更可控,因为可以结合安全重启周期。
代码示例:
<?php
use Swoole\Server;
$server = new Swoole\Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 4,
// 设置 reload_async 为 true 开启异步安全重启
'reload_async' => true,
// 最大等待时间(秒),防止 Worker 进程卡死
'max_wait_time' => 30,
]);
$server->on('WorkerStart', function ($server, $workerId) {
// 每个 Worker 启动时加载代码
require __DIR__ . '/app_code.php';
});
$server->on('Receive', function ($server, $fd, $reactorId, $data) {
// 处理业务逻辑...
$server->send($fd, 'Hello');
});
$server->start();
// --- 在某个控制台命令或管理页面中 ---
// 注意:你不能在 on('Receive') 回调中调用 reload(),因为它属于 Worker 进程。
// reload() 应该由 Master 进程或用户自定义的进程来调用。
// 假设你通过 HTTP API 触发重启
$httpServer = new Swoole\Http\Server("0.0.0.0", 9502);
$httpServer->on('request', function ($request, $response) use ($server) {
// 通过向主 Server 发送自定义协议消息触发重启
// 更推荐的方式是让一个常驻的 Manager 脚本来管理
if ($request->server['request_uri'] == '/reload') {
$result = $server->reload(); // 返回 true 或 false
if ($result) {
$response->end("reload success");
} else {
$response->end("reload failed");
}
}
});
$httpServer->start();
关键点:reload() 方法必须在 Master 进程 的上下文中调用,通常的做法是:
- 启动一个独立的 Swoole 管理服务(如 HTTP 服务)。
- 在管理服务中接收指令,调用主业务 Server 的
reload()。
使用 Swoole 官方管理工具(如 Swoole 管理面板或 Supervisord 配合)
虽然信号是底层机制,但在生产环境中,通常配合以下工具:
-
Supervisord + 信号:
- 在 Supervisor 配置文件的
[program:your_app]中添加stopasgroup=true和killasgroup=true。 - 执行
supervisorctl signal USR1 your_app。 - 注意:Supervisor 默认的
stop或restart命令是发送SIGTERM,这是强杀,不是平滑,必须明确使用signal USR1。
- 在 Supervisor 配置文件的
-
自定义管理脚本(推荐): 编写一个 bash 脚本,让你可以像操作 Nginx 一样操作 Swoole。
#!/bin/bash # swoole_manager.sh PID_FILE="/var/run/swoole_app.pid" SERVER_SCRIPT="/path/to/your_server.php" case "$1" in start) php $SERVER_SCRIPT ;; stop) kill -TERM $(cat $PID_FILE) ;; reload) echo "Sending reload signal to Swoole Master PID: $(cat $PID_FILE)" kill -USR1 $(cat $PID_FILE) echo "Reload signal sent." ;; *) echo "Usage: $0 {start|stop|reload}" exit 1 esac
关键配置参数(提高平滑重启的可靠性)
为了确保平滑重启真正平滑,需要在 Server::set() 中设置以下参数:
| 配置项 | 默认值 | 说明 |
|---|---|---|
reload_async |
false |
强烈建议设为 true,启用异步安全重启,开启后,Manager 会等待 Worker 进程的所有异步连接关闭、事件循环清空后才退出,有效防止 TCP 连接断开、Redis/MySQL 连接未释放等问题。 |
max_wait_time |
3 |
当 reload_async = true 时,设置 Worker 进程退出前的最大等待时间(秒),Worker 在等待时间内没有退出(例如有长时间阻塞的任务),Swoole 会强制杀死它。 |
max_request |
0 |
设置每个 Worker 进程处理完 max_request 次请求后自动重启,这可以很好地解决 PHP 脚本的内存泄漏问题,平滑重启会自动触发。 |
推荐配置:
$server->set([
'worker_num' => 4,
'reload_async' => true,
'max_wait_time' => 60,
'max_request' => 10000, // 处理1万次请求后自动重启
]);
常见问题与坑
-
代码未更新?
- 原因:
onWorkerStart中的require或include被 OPcache 缓存。 - 解决:确保在
onWorkerStart中加载核心业务代码,而不是在全局一次性加载,或者配合opcache.revalidate_freq = 0或opcache.validate_timestamps = 1使用。最推荐的做法是:在onWorkerStart中动态require框架的入口文件。
- 原因:
-
长时间阻塞连接(如 WebSocket 或长连接 TCP)?
- 问题:
reload_async = true虽然会等待,但如果业务逻辑不检查Server::shutdown()状态,可能会导致无法退出。 - 解决:在业务逻辑中定期调用
$server->shutdown()来判断服务是否在重启中,或者在onWorkerStop中手动清理连接。
- 问题:
-
Task Worker 的平滑重启?
- 使用
kill -USR2 <pid>信号,或者调用$server->reload()方法(它默认只重启 Worker,会忽略 Task Worker),如需重启 Task Worker,需调用$server->taskworker_reload()。
- 使用
总结最佳实践
- 设置
reload_async = true。 - 设置合理的
max_wait_time和max_request。 - 在
onWorkerStart中加载业务代码(而不是在全局)。 - 使用自定义管理脚本或 Supervisor 的
signal命令。 - 重启后检查日志:查看是否有
WARNING: WorkerX timed或WARNING: WorkerX exit timeout的警告,如果有,说明max_wait_time设置过短或业务逻辑有长时间阻塞。
执行命令:
# 实际生产中最常用的就是这一行命令 kill -USR1 $(cat /var/run/swoole.pid)