Swoole 的自定义进程(Process)
你目前是通过 think-swoole 框架提供的沙箱(Sandbox)在 swoole.init 事件中,使用主服务器的 $server->addListener(...) 来动态监听 RPC 端口(9513)。
正如之前分析的,$server->addListener() 产生的子端口,无法通过 $rpcPort->set() 独立设置 worker_num。因此,你的 receive 回调虽然使用了 go() 开启协程,但它们依然运行在主服务的 Worker 进程中,一旦 Websocket 发生大量广播,Worker 进程的事件循环(Event Loop)被密集 I/O 或同步循环占满,依然会拖慢 RPC 的处理。
针对你现有的结构,由于你已经把协议解析和沙箱绑定写得非常完善(依赖了 Sandbox 保持容器干净),最优雅、改动最小的隔离方式是利用 Swoole 的自定义进程(Process),在其中单独运行一个完全独立的 Server。
下面是为你定制的隔离改造方案,完美保留你原有的 Sandbox 沙箱模式和协议逻辑:
1. 新建独立进程类:app/common/swoole/process/RpcServerProcess.php
我们将原本写在 addListener 里的逻辑,平移到一个独立的自定义进程中,通过原生 Swoole\Server 启动。它会拥有物理隔离的 2 个 Worker 进程,和 Websocket 彻底分家。
php
<?php
namespace app\common\swoole\process;
use think\swoole\contract\ProcessInterface;
use Swoole\Process as SwooleProcess;
use Swoole\Server as SwooleServer;
use think\App;
class RpcServerProcess implements ProcessInterface
{
protected $app;
// 注入系统的 App 实例
public function __construct(App $app)
{
$this->app = $app;
}
public function handle(SwooleProcess $process)
{
// 1. 从配置中读取 RPC 服务的 host 和 port
$config = $this->app->config->get('swoole.rpc_server');
// 2. 创建一个绝对独立的纯 TCP Server
$server = new SwooleServer($config['host'], $config['port'], SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
// 3. 🔥 在这里可以真正独立设置 RPC 专属的 Worker 进程数了!
$server->set([
'worker_num' => 2, // 专供 RPC 使用的独立进程数,不与 WS 共享
'open_eof_check' => true, // 沿用你原有的换行符分包协议
'package_eof' => "\n",
'open_eof_split' => true,
'max_wait_time' => 5,
]);
// 获取系统的沙箱 Sandbox 实例(保持原有的容器隔离设计)
$sandbox = $this->app->make(\think\swoole\Sandbox::class);
// 4. 绑定独立的 receive 事件
$server->on('receive', function (SwooleServer $serv, int $fd, int $reactorId, string $data) use ($sandbox) {
go(function () use ($serv, $fd, $reactorId, $data, $sandbox) {
$sandbox->run(function () use ($serv, $fd, $reactorId, $data, $sandbox) {
// 调用你原本就写好的 RPC 处理类
$rpcService = $sandbox->getApplication()->make(\app\common\swoole\rpc\RpcService::class);
$rpcService->handleRpcRequest($serv, $fd, $reactorId, $data);
});
});
});
// 设置 RPC 的进程名称
$server->on('start', function () use ($config) {
swoole_set_process_name($config['process_name'] ?? 'swoole: rpc_master');
});
// 5. 启动独立的 RPC 服务器
$server->start();
}
}
请谨慎使用此类代码。
2. 在 config/swoole.php 中注册该进程
打开你的 config/swoole.php 配置,在 custom_process 配置项中将上面写好的类注册进去:
php
return [
// ... 其他配置
'custom_process' => [
// 注册 RPC 独立进程
'rpc_server_process' => \app\common\swoole\process\RpcServerProcess::class,
],
];
请谨慎使用此类代码。
3. 精简清理你原有的 SwooleService.php
现在 RPC 已经搬到了独立的物理进程中,你需要把原有 SwooleService.php 中关于 RPC 端口监听(addListener)、RPC 的 receive 回调、以及 RPC 的 start/managerStart 修改进程名 的代码全部删掉。只留下 Websocket 的专属逻辑。
清理后的 SwooleService.php 如下所示:
php
<?php
namespace app\common\swoole;
use think\Service;
use think\swoole\Manager;
use Swoole\Server as SwooleServer;
class SwooleService extends Service
{
public function boot()
{
if ($this->app->runningInConsole() && class_exists(Manager::class))
{
$this->app->event->listen('swoole.init', function ()
{
/** @var Manager $manager */
$manager = $this->app->make(Manager::class);
$server = $manager->getServer();
// 移除原有的 $server->addListener('9513') 逻辑...
// 移除原有的 $rpcPort->on('receive') 逻辑...
// 获取 Sandbox 实例
$sandbox = $this->app->make(\think\swoole\Sandbox::class);
// 仅保留 WebSocket 相关的全局回调(9512 端口主服务)
// WebSocket 握手
$server->on('handshake', function ($request, $response) use ($sandbox)
{
go(function () use ($request, $response, $sandbox)
{
$sandbox->run(function () use ($request, $response, $sandbox)
{
$WebSocketService = $sandbox->getApplication()->make(\app\common\swoole\websocket\Service::class);
$WebSocketService->onHandshake($request, $response);
});
});
});
// WebSocket 协议
$server->on('request', function ($request, $response) use ($sandbox)
{
go(function () use ($request, $response, $sandbox)
{
$sandbox->run(function () use ($request, $response, $sandbox)
{
$WebSocketService = $sandbox->getApplication()->make(\app\common\swoole\websocket\Service::class);
$WebSocketService->handleHttpRequest($request, $response);
});
});
});
});
}
}
}
请谨慎使用此类代码。
💡 这样做改造的精妙之处:
零业务代码重构:你不需要去修改
\app\common\swoole\rpc\RpcService::class内部的任何一行代码,它依然通过相同的$sandbox运行,接收相同的参数。真正的 Worker 进程隔离:在
RpcServerProcess中,通过new SwooleServer的worker_num => 2专门启动了 2 个 Worker 进程。此时,9512 端口(WS)拥有一批 Worker,9513 端口(RPC)拥有另一批独立的 Worker。Websocket 无论如何进行同步大批量广播,都绝对不会对 RPC 的响应造成任何波动。一键启停:当你执行
php think swoole restart时,think-swoole框架会自动拉起这个custom_process,RPC 服务器会随着主服务同步启动和关闭,管理依旧非常方便。
如果你的 RPC 客户端(调用方)使用的是长连接,建议确认一下客户端是否配置了断线重连机制?因为这次改造会让 RPC 作为一个单独的进程服务启动,在首次重启服务时,长连接会触发一次断开重连。