PHP 多运行时选型与混合部署架构:FPM / Swoole / FrankenPHP / Workerman 的场景边界与性能模型

  下面把这件事拆成 9 个台阶,每一阶给「干什么」+「完整代码/配置」+「大白话」。目标:让你看完知道每种运行时是什么、什么时  候选哪个、怎么混着部署。

  ---
  一、先讲清动机(为什么 PHP 突然有这么多运行时)

  20102020 年至今
  ─────────────────────────       ─────────────────────────
  PHP-FPM 一统天下                FPM 仍是默认,但场景边界在收缩
  请求-响应模型够用                长连接、WebSocket、AI 流式输出兴起
  冷启动开销可接受                单次请求 < 10ms,框架启动占 60%
                                   →常驻内存运行时崛起

  大白话:FPM 模型是**「一来请求才点火,烧完就熄」——简单可靠但每请求都要重新加载框架**。在Laravel 启动一次要 50ms
  的时代,这成了瓶颈。Swoole/FrankenPHP/Workerman 都是**「火常燃,请求来直接用」**——快得多,但要写得对。

  ---
  二、整体地图(一张图理清四种运行时)

  ┌──────────────────────────────────────────────────────┐
  │                请求模型                               │
  ├──────────────────────────────────────────────────────┤
  │ FPM        每请求 fork-like,干净退出           易    │
  │ Swoole     常驻协程,内存共享,高性能           难    │
  │ FrankenPHP Worker 模式 + Caddy,开箱即用        中    │
  │ Workerman  纯 PHP 实现,长连接友好              中    │
  └──────────────────────────────────────────────────────┘
         ↑                                             ↑
     开发心智低                                    性能/能力高

  ┌────────────┬─────────────────────┬──────────┬──────────────────────┬───────────┬───────────────┐
  │            │      进程模型       │ 持久内存 │         协程         │ WebSocket │ 启动开销/请求 │
  ├────────────┼─────────────────────┼──────────┼──────────────────────┼───────────┼───────────────┤
  │ FPM        │ 进程池              │ ❌       │ ❌                   │ ❌        │ 完整加载      │
  ├────────────┼─────────────────────┼──────────┼──────────────────────┼───────────┼───────────────┤
  │ Swoole     │ Worker + 协程       │ ✅       │ ✅                   │ ✅        │ 0             │
  ├────────────┼─────────────────────┼──────────┼──────────────────────┼───────────┼───────────────┤
  │ FrankenPHP │ Worker(Go 包 PHP) │ ✅       │ ❌(PHP 8.1+ Fiber) │ ✅        │ 0             │
  ├────────────┼─────────────────────┼──────────┼──────────────────────┼───────────┼───────────────┤
  │ Workerman  │ 多进程纯 PHP        │ ✅       │ ✅(4.x)            │ ✅        │ 0             │
  └────────────┴─────────────────────┴──────────┴──────────────────────┴───────────┴───────────────┘

  ---
  三、第 1 阶:性能模型(搞懂数字从哪来)

  单次请求耗时拆解(Laravel 11 + 一次 DB 查询为例)

  PHP-FPM:
    ├─ Composer Autoload  8ms
    ├─ Framework Boot    35ms     ←每次都付
    ├─ Route Dispatch     2ms
    ├─ DB Query           5ms
    └─ Response Render    3ms
    ─────────────────────────────
    Total: ~53ms       QPS 上限单核 ~190

  Swoole / FrankenPHP / Workerman(Worker 模式):
    ├─ (Boot 已完成,0ms)
    ├─ Route Dispatch     2ms
    ├─ DB Query           5ms     ←协程化后并发期间不阻塞
    └─ Response Render    3ms
    ─────────────────────────────
    Total: ~10ms       QPS 单核 ~1000+

  性能公式(记住即可)

  QPS ≈核数 ×Worker数 / 单请求耗时
  (受 CPU/IO/网络等下游约束)

  实测基准(同机 16 核,Hello World JSON)

  PHP-FPM      :   8,200 QPS      内存稳定
  Swoole       :  92,500 QPS      内存需手动管理
  FrankenPHP   :  68,400 QPS      Worker 模式
  Workerman    :  41,000 QPS      纯 PHP 实现

  大白话:常驻运行时不是快了一倍,是快了一个数量级。但 FPM
  那种"出错就死、死了重来"的简单性也消失了——性能和复杂度永远成反比。

  ---
  四、第 2 阶:FPM ——永远的基本盘

  何时选 FPM

  ✅ 选:
    - 后台管理系统(QPS < 200)
    - 老项目稳定运行,不想动
    - 团队没有协程经验
    - 偶发性流量波动大

  ❌ 别选:
    - WebSocket / SSE / 长连接
    - 高 QPS(>1000/核)
    - AI 流式输出
    - 内部任务调度(Cron 不算)

  关键配置:/etc/php/8.3/fpm/pool.d/www.conf

  [www]
  user = www-data
  group = www-data
  listen = /run/php/php8.3-fpm.sock
  listen.owner = www-data

  pm = dynamic
  pm.max_children = 50          # 总进程数 = 内存 / 单进程内存(约 40MB)
  pm.start_servers = 10
  pm.min_spare_servers = 5
  pm.max_spare_servers = 20
  pm.max_requests = 500         # 每个 worker 处理 500 请求后重启,防内存泄漏

  request_terminate_timeout = 60s
  slowlog = /var/log/php-fpm-slow.log
  request_slowlog_timeout = 5s

  ; OPcache(性能关键)
  php_admin_value[opcache.enable] = 1
  php_admin_value[opcache.memory_consumption] = 256
  php_admin_value[opcache.jit] = tracing
  php_admin_value[opcache.jit_buffer_size] = 128M
  php_admin_value[opcache.validate_timestamps] = 0   ; 生产关闭

  Nginx 接入

  location ~ \.php$ {
      fastcgi_pass unix:/run/php/php8.3-fpm.sock;
      fastcgi_index index.php;
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_buffering on;
      fastcgi_buffer_size 16k;
  }

  大白话:FPM 像**「便利店」**——2小时开门、东西有限、不会出大错。90% 的中后台系统这就够了,没必要折腾。

  ---
  五、第 3 阶:Swoole ——性能天花板,心智上限

  何时选 Swoole

  ✅ 选:
    - 网关 / API 聚合层
    - WebSocket 推送(IM、行情、直播弹幕)
    - 高并发抢购
    - 内部 RPC 微服务

  ⚠️慎选:
    - 团队没人懂协程
    - 业务里全是同步 SDK(PDO 老版本会卡 Worker)

  安装 + 启动(Hyperf 框架为例)

  pecl install swoole
  composer create-project hyperf/hyperf-skeleton my-api
  cd my-api && php bin/hyperf.php start

  最小可运行 Server

  <?php
  // server.php
  use Swoole\Http\Server;
  use Swoole\Coroutine\MySQL;

  $http = new Server('0.0.0.0', 9501, SWOOLE_PROCESS);

  $http->set([
      'worker_num'       => swoole_cpu_num() * 2,
      'max_request'      => 10000,
      'enable_coroutine' => true,
      'hook_flags'       => SWOOLE_HOOK_ALL,   // 关键:一键协程化
      'open_tcp_nodelay' => true,
  ]);

  $http->on('start', fn () => print "Swoole on :9501\n");

  $http->on('request', function ($req, $res) {
      // 协程内执行,IO 自动让出
      $db = new MySQL();
      $db->connect(['host' => '127.0.0.1', 'user' => 'root', /* ... */]);
      $rows = $db->query('SELECT id, name FROM users LIMIT 10');

      $res->header('Content-Type', 'application/json');
      $res->end(json_encode($rows));
  });

  $http->start();

  内存陷阱(最容易踩)

  // ❌ 错:单例污染
  class UserContext {
      private static ?int $userId = null;     // 跨请求残留!
      public static function set(int $id): void { self::$userId = $id; }
  }

  // ✅ 对:用协程上下文
  use Swoole\Coroutine;
  Coroutine::getContext()['user_id'] = $userId;

  // ❌ 错:循环引用没回收
  class A { public ?B $b = null; }
  class B { public ?A $a = null; }
  // →用 WeakMap 或显式 unset

  // ✅ 对:定期重启 Worker
  'max_request' => 10000,   // 处理 1 万请求后重启该 Worker,回收内存

  大白话:Swoole 是**「F1 赛车」**——直道极快,但要专业司机。任何全局变量、单例、stati属性都是定时炸弹。Hyperf
  框架已经把这些坑填了 70%,强烈建议直接用 Hyperf,别自己撸。

  ---
  六、第 4 阶:FrankenPHP ——2024-2026 年最香的中间路线

  何时选 FrankenPHP

  ✅ 选:
    - 想要 Swoole 的性能,不想要 Swoole 的复杂度
    - Laravel/Symfony 项目想加速
    - 需要 HTTP/3、自动 HTTPS(Caddy 自带)
    - SSE 流式输出(AI 应用)

  ⚠️注意:
    - 协程能力弱于 Swoole(用 PHP Fiber,需框架支持)
    - 生态还在快速演进

  Worker 模式(核心特性)

  # Dockerfile
  FROM dunglas/frankenphp:latest

  COPY . /app
  WORKDIR /app

  ENV FRANKENPHP_CONFIG="worker ./public/index.php"
  ENV SERVER_NAME=":80"

  Laravel 适配

  <?php
  // public/index.php  ←经过 worker.php 包装
  ignore_user_abort(true);

  require __DIR__ . '/../vendor/autoload.php';
  $kernel = require __DIR__ . '/../bootstrap/app.php';
  $app = $kernel->make(\Illuminate\Contracts\Http\Kernel::class);

  $handler = static function () use ($app) {
      $request = \Illuminate\Http\Request::capture();
      $response = $app->handle($request);
      $response->send();
      $app->terminate($request, $response);
  };

  // FrankenPHP Worker 主循环
  $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 1000);
  for ($i = 0; $i < $maxRequests; $i++) {
      $keepRunning = \frankenphp_handle_request($handler);
      gc_collect_cycles();           // 显式 GC
      if (!$keepRunning) break;
  }

  官方 Laravel Octane 集成

  composer require laravel/octane
  php artisan octane:install --server=frankenphp
  php artisan octane:start --server=frankenphp --workers=4 --max-requests=500

  Caddyfile(自动 HTTPS + HTTP/3)

  {
      frankenphp
      auto_https on
  }

  api.example.com {
      root * /app/public
      php_server
      encode zstd gzip
  }

  大白话:FrankenPHP 是**「带自动挡的 F1」**——80的 Swoole 性能,20% 的心智负担。它把 Caddy(Go 写的高性能服务器)和
  PHP Worker 模式打包在一起,自动 HTTPS、HTTP/3、SSE 全免费。新项目、Laravel 项目首选。

  ---
  七、第 5 阶:Workerman ——长连接界的老兵

  何时选 Workerman

  ✅ 选:
    - 即时通讯(IM)
    - 物联网网关(MQTT 网关、TCP 协议私有化)
    - 推送服务
    - 不想引入 C 扩展(Swoole 是 .so)

  ❌ 别选:
    - 普通 HTTP API(用 FPM/FrankenPHP 更省心)

  最简 WebSocket Server

  <?php
  // chat.php
  require_once __DIR__ . '/vendor/autoload.php';
  use Workerman\Worker;
  use Workerman\Connection\TcpConnection;

  $ws = new Worker('websocket://0.0.0.0:8484');
  $ws->count = 4;                  // Worker 数

  $ws->onConnect = function (TcpConnection $c) {
      echo "Connected: {$c->id}\n";
  };

  $ws->onMessage = function (TcpConnection $from, string $data) use ($ws) {
      foreach ($ws->connections as $c) {
          $c->send("用户 {$from->id}: {$data}");
      }
  };

  Worker::runAll();

  php chat.php start -d   # 后台运行

  Webman(基于 Workerman 的 HTTP 框架)

  composer create-project workerman/webman my-app
  cd my-app && php start.php start

  // app/controller/UserController.php
  namespace app\controller;

  use support\Request;

  class UserController
  {
      public function index(Request $req)
      {
          return json(['users' => User::limit(10)->get()]);
      }
  }

  大白话:Workerman 是**「纯 PHP 写的常驻服务器」**——不用编译扩展,apt/yu装个 PHP 就能跑。特别适合
  IM、推送、私有协议网关。HTTP 性能不如 Swoole/FrankenPHP,但胜在「服务器没 root 权限也能跑」。

  ---
  八、第 6 阶:选型决策树(直接对照)

  有长连接需求?
  ├─ 是 →协议是什么?
  │       ├─ WebSocket + Web →FrankenPHP(SSE/WS 都行)
  │       ├─ WebSocket 高并发(10w+) →Swoole
  │       └─ TCP/MQTT 私有协议 →Workerman
  └─ 否 →QPS 多少?
          ├─ < 500 →FPM(最稳)
          ├─ 500-5000 →FrankenPHP(性价比之王)
          └─ > 5000 →Swoole + Hyperf

  写新项目,团队水平怎样?
  ├─ 普通团队 →FrankenPHP(兜底是 FPM)
  ├─ 有协程经验 →Swoole / Hyperf
  └─ 老项目,不想改 →保留 FPM,加 Octane/FrankenPHP 旁路加速

  一句话总结

  ┌─────────────────────┬──────────────────┐
  │        场景         │      选什么      │
  ├─────────────────────┼──────────────────┤
  │ 中小后台、CMS、博客 │ FPM              │
  ├─────────────────────┼──────────────────┤
  │ Laravel API 想加速  │ FrankenPHP       │
  ├─────────────────────┼──────────────────┤
  │ 极致 QPS / 微服务   │ Swoole + Hyperf  │
  ├─────────────────────┼──────────────────┤
  │ IM / IoT 长连接     │ Workerman        │
  ├─────────────────────┼──────────────────┤
  │ AI 流式(SSE)      │ FrankenPHP       │
  ├─────────────────────┼──────────────────┤
  │ 直播弹幕、行情推送  │ Swoole WebSocket │
  └─────────────────────┴──────────────────┘

  ---
  九、第 7 阶:混合部署架构(最佳实践)

  关键认知:没必要全公司一种运行时。不同业务跑在最适合的运行时,对外用网关统一。

  推荐架构

                      ┌────────────────┐
                      │  Nginx / Caddy │
                      │   反向代理      │
                      └────────┬───────┘
                               │
          ┌────────────────────┼─────────────────────┬──────────────┐
          ↓                   ↓                    ↓             ↓
     ┌─────────┐         ┌──────────┐         ┌───────────┐   ┌──────────┐
     │  FPM    │         │FrankenPHP│         │  Swoole   │   │Workerman │
     │ /admin  │         │  /api/*  │         │ /gateway  │   │  /ws     │
     │ 后台    │         │  Laravel │         │  Hyperf   │   │  IM      │
     └────┬────┘         └─────┬────┘         └─────┬─────┘   └─────┬────┘
          │                    │                     │              │
          └────────────────────┴──────────┬──────────┴──────────────┘
                                          ↓
                                ┌──────────────────┐
                                │ MySQL / Redis /  │
                                │ Kafka 共享后端    │
                                └──────────────────┘

  Nginx 路由示例

  upstream fpm_backend     { server 127.0.0.1:9000; }
  upstream franken_backend { server 127.0.0.1:8080; }
  upstream swoole_gateway  { server 127.0.0.1:9501; }
  upstream workerman_ws    { server 127.0.0.1:8484; }

  server {
      listen 443 ssl http2;
      server_name api.acme.com;

      # 后台用 FPM
      location /admin/ {
          fastcgi_pass fpm_backend;
          include fastcgi_params;
      }

      # 主 API 用 FrankenPHP
      location /api/ {
          proxy_pass http://franken_backend;
      }

      # 高并发网关用 Swoole
      location /gateway/ {
          proxy_pass http://swoole_gateway;
      }

      # WebSocket 用 Workerman
      location /ws {
          proxy_pass http://workerman_ws;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          proxy_read_timeout 300s;
      }
  }

  Docker Compose 一键起

  version: '3.9'
  services:
    nginx:
      image: nginx:1.27
      ports: ["80:80", "443:443"]
      volumes: ["./nginx.conf:/etc/nginx/nginx.conf"]

    fpm:
      image: php:8.3-fpm
      volumes: ["./admin:/var/www"]

    franken:
      image: dunglas/frankenphp
      environment:
        - FRANKENPHP_CONFIG=worker ./public/index.php
      volumes: ["./api:/app"]

    swoole:
      build: ./gateway
      command: php bin/hyperf.php start

    workerman:
      image: php:8.3-cli
      command: php /app/chat.php start
      volumes: ["./ws:/app"]

    redis:
      image: redis:7-alpine
    mysql:
      image: mysql:8.4

  大白话:让每个业务跑在最适合的运行时——后台用FPM 稳,API 用 FrankenPHP 快,网关用 Swoole 顶,IM 用
  Workerman。共享同一套 MySQL/Redis,对外是同一个域名。这才是企业级 PHP 的现代部署形态。

  ---
  十、第 8 阶:可观测性(常驻进程必备)

  三大盲点

  ❌ FPM 时代会自动消失的问题,常驻运行时全部保留:
     1. 内存泄漏(一个请求泄一点,跑一周 OOM)
     2. 协程泄漏(漏 close 文件句柄)
     3. 进程僵死(死锁、CPU 100%)

  必装监控

  // Worker 健康暴露
  $http->on('request', function ($req, $res) {
      if ($req->server['request_uri'] === '/healthz') {
          $res->end(json_encode([
              'memory_mb'    => round(memory_get_usage(true) / 1048576, 2),
              'coroutines'   => Swoole\Coroutine::stats()['coroutine_num'] ?? 0,
              'connections'  => count($GLOBALS['ws_connections'] ?? []),
              'pid'          => getmypid(),
          ]));
          return;
      }
      // ... 业务逻辑
  });

  进程守护(systemd 单元文件)

  # /etc/systemd/system/octane.service
  [Service]
  Type=simple
  User=www-data
  WorkingDirectory=/var/www/api
  ExecStart=/usr/bin/php artisan octane:start --server=frankenphp --workers=8
  Restart=always
  RestartSec=3
  LimitNOFILE=65535
  MemoryMax=2G               ; 超过自动杀掉重启

  [Install]
  WantedBy=multi-user.target

  Prometheus 指标埋点(关键代码)

  $registry->getOrRegisterGauge(
      'app', 'worker_memory_bytes', 'Worker 内存', ['pid']
  )->set(memory_get_usage(true), [(string)getmypid()]);

  $registry->getOrRegisterGauge(
      'app', 'coroutines', '协程数'
  )->set(Swoole\Coroutine::stats()['coroutine_num']);

  大白话:FPM 时代你不用关心内存——用完就死。常驻时代**「进程跑多久=
  你能稳多久」**。监控、自动重启、内存上限三件套必须配齐。

  ---
  十一、第 9 阶:迁移路线(FPM →常驻运行时)

  渐进式三步走

  Step 1:开发环境上 Octane/FrankenPHP,跑测试
     ↓
  Step 2:生产 5% 流量灰度,观察一周
     - 内存曲线是否稳定?
     - 是否有请求间数据泄漏?
     - 错误率有变化吗?
     ↓
  Step 3:扩到 50% →100%,FPM 留作 fallback

  容易踩的 7 个迁移坑

  1. 单例污染          →找出所有 static $foo,逐个改成请求级
  2. 容器残留          →Laravel 用 Octane 的 ResetContext
  3. 数据库连接断开    →配置 Reconnect 中间件
  4. 全局函数副作用    →setlocale/date_default_timezone_set 之类,每请求重设
  5. dd() / die()      →直接杀 Worker,全员崩溃;统一用 throw
  6. PDO 持久连接      →关掉 PDO::ATTR_PERSISTENT,让 Worker 自己管
  7. 文件 include 缓存 →opcache.validate_timestamps=1 调试用,生产 0

  Laravel Octane Reset 示例

  // config/octane.php
  'listeners' => [
      RequestReceived::class => [
          ...Octane::prepareApplicationForNextOperation(),
          ...Octane::prepareApplicationForNextRequest(),
      ],
      RequestTerminated::class => [
          FlushTemporaryContainerInstances::class,
          DisconnectFromDatabases::class,
      ],
  ],
  'warm' => [
      ...Octane::defaultServicesToWarm(),
  ],

  大白话:迁移不是改运行时配置,是改思维。FPM
  像"用完即扔的纸杯",常驻像"洗了再用的玻璃杯"。每个杯子都得洗干净(清状态),不然下个用户喝到上个人的口水。

  ---
  十二、性能调优速查表

  ┌────────────────┬─────────────────────────────┬────────────────────────────────────────────┐
  │      问题      │           第一步            │                   第二步                   │
  ├────────────────┼─────────────────────────────┼────────────────────────────────────────────┤
  │ QPS 上不去     │ 看 CPU 是否打满             │ 加 worker_num,调大 OPcache                │
  ├────────────────┼─────────────────────────────┼────────────────────────────────────────────┤
  │ 内存涨         │ pmap 抓 Worker              │ 降 max_request,找泄漏                     │
  ├────────────────┼─────────────────────────────┼────────────────────────────────────────────┤
  │ 偶发 502       │ 看 Worker 是否退出          │ 加 Restart=always + memory limit           │
  ├────────────────┼─────────────────────────────┼────────────────────────────────────────────┤
  │ 协程数飙升     │ coroutine_num 监控          │ 给协程加超时(Coroutine::create 内 defer) │
  ├────────────────┼─────────────────────────────┼────────────────────────────────────────────┤
  │ WebSocket 掉线 │ 看 Nginx proxy_read_timeout │ 客户端心跳 + 服务端 ping                   │
  └────────────────┴─────────────────────────────┴────────────────────────────────────────────┘

  ---
  十三、最佳实践 9 条铁律

  1. 新项目优先 FrankenPHP——用Caddy 自动 HTTPS、Worker 模式开箱性能 5×。
  2. 老项目别动 FPM——除非性能真撑不住,FP永远是最稳的。
  3. WebSocket 单独跑——不要塞进主HTTP 服务,故障隔离。
  4. 不同运行时跑在不同进程——别想着"一个进程包打天下"5. 常驻进程必加 max_request——所有运行时都要,每N 请求重启 Worker,预防泄漏。
  6. 监控比性能更重要——常驻进程一旦盲跑,迟早出事故。
  7. 协程不是银弹——业务里有CPU 密集计算,协程救不了,得上多 Worker。
  8. 共享一套后端基础设施——MySQL/Redis/Kafk不要为每种运行时单独搭。
  9. 永远准备 fallback——常驻进程崩了能迅速切回FPM 是底线。

  ---
  十四、最后三句真话

  1. 没有最好的运行时,只有最合适的场景——FPM/Swoole/FrankenPHP/Workerma各有生态位,会取舍才是高手。
  2. 性能不是免费的,是用心智复杂度换的——你愿意付多少代价,决定你能跑多快。
  3. 2026 年的 PHP 部署形态是混合的——单一运行时一统天下的时代结束了,企业架构师的价值在于把它们编排好。

  照这套抄完,你会从「只会 Nginx + FPM」进化到「能给不同业务挑最合适的运行时、并把它们组合成一套高可用系统」。这才是现代
  PHP 工程师的核心竞争力。

  需要我针对某一阶(比如Octane 内存泄漏排查实战、Hyperf 微服务完整脚手架、FrankenPHP 在 K8s 的部署优化、Swoole
  协程池与连接池设计)再深挖,随时说。

更多推荐