avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 Swoole 的自定义进程(Process)
文章

Swoole 的自定义进程(Process)

发表于 2026-06-29 更新于 2026-06- 29
作者 mdo
16~21 分钟 阅读

你目前是通过 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);
                        });
                    });
                });
            });
        }
    }
}

请谨慎使用此类代码。

💡 这样做改造的精妙之处:

  1. 零业务代码重构:你不需要去修改 \app\common\swoole\rpc\RpcService::class 内部的任何一行代码,它依然通过相同的 $sandbox 运行,接收相同的参数。

  2. 真正的 Worker 进程隔离:在 RpcServerProcess 中,通过 new SwooleServer 的 worker_num => 2 专门启动了 2 个 Worker 进程。此时,9512 端口(WS)拥有一批 Worker,9513 端口(RPC)拥有另一批独立的 Worker。Websocket 无论如何进行同步大批量广播,都绝对不会对 RPC 的响应造成任何波动。

  3. 一键启停:当你执行 php think swoole restart 时,think-swoole 框架会自动拉起这个 custom_process,RPC 服务器会随着主服务同步启动和关闭,管理依旧非常方便。

如果你的 RPC 客户端(调用方)使用的是长连接,建议确认一下客户端是否配置了断线重连机制?因为这次改造会让 RPC 作为一个单独的进程服务启动,在首次重启服务时,长连接会触发一次断开重连。

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

为 RPC 端口和 Websocket 端口配置独立的 Worker 进程

上一篇

在 think-swoole 框架中融入 Custom Process(自定义进程)

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。