新一代 PHP Runtime 融合工程:FPM / Swoole / FrankenPHP 混合部署的场景边界与协同架构

  下面把这件事拆成 10 个台阶,每一阶给「干什么」+「完整代码/配置」+「大白话」。目标:让一个 PHP 系统从"全栈用
  FPM""全栈用 Swoole"进化到"不同业务跑在最适合的 Runtime,统一治理、统一观测、统一演进"。

  ---
  一、先讲清动机(为什么是融合,不是替代)

  单一 Runtime 时代                     融合 Runtime 时代
  ─────────────────────────             ─────────────────────────
  全栈 FPM:稳但慢                      后台 FPM、网关 Swoole、API FrankenPHP
  全栈 Swoole:快但坑多                 各业务跑最适合的形态
  全栈 FrankenPHP:中庸但生态新         统一观测、统一发布、统一治理
  追求"一种 Runtime 通吃"               追求"组合最优解"

  关键认知:没有最好的 PHP Runtime,只有最适合某场景的 Runtime。

  后台管理(QPS 50)        →FPM 最合适
  对外 API(QPS 5000)      →FrankenPHP 最香
  网关/聚合(QPS 50000+)   →Swoole + Hyperf 顶得住
  WebSocket / IM           →Workerman 经济实惠
  AI 流式输出 / SSE        →FrankenPHP 原生支持

  大白话:2026 年的 PHP 不是单一 Runtime 一统天下,而是分场景组合。就像企业不会全员只用一种笔记本电脑——文员用
  Mac、程序员用 Linux、设计师用高配 Windows。Runtime 融合工程,就是教你怎么把多种 Runtime
  部署在一起,既各取所长,又不混乱失控。

  ---
  二、整体地图(10 阶段全景)

  ┌──────────────────────────────────────────────────────┐
  │ 第10阶:演进路线(从单 Runtime 到融合架构)          │
  │ 第9阶:跨 Runtime 治理(配置/秘钥/发布统一)         │
  │ 第8阶:统一可观测性(三 Runtime 共用一套监控)       │
  │ 第7阶:跨 Runtime 协作(共享 SDK / 协议)             │
  │ 第6阶:K8s 中的多 Runtime 部署模式                   │
  │ 第5阶:边界协议(网关 / 服务发现 / 负载均衡)        │
  │ 第4阶:Runtime 选型决策树(精确到接口)              │
  │ 第3阶:三大 Runtime 深度剖析(FPM/Swoole/Franken)    │
  │ 第2阶:融合架构整体形态                              │
  │ 第1阶:心智模型(为什么不要"单一 Runtime 信仰")     │
  └──────────────────────────────────────────────────────┘

  ---
  三、第 1 阶:心智模型(打破单一 Runtime 信仰)

  三种典型错误信仰

  ❌ 信仰 1:"我们必须全栈 Swoole"
    问题:简单后台没 QPS,Swoole 反而带来心智负担
    内存泄漏、单例污染——后台代码全炸

  ❌ 信仰 2:"FPM 永远不变"
    问题:对外 API 性能上不去,招人也难
    错过 5x 性能红利

  ❌ 信仰 3:"哪个最新就用哪个"
    问题:FrankenPHP 啥都干,WebSocket 也硬塞
    生态不成熟的部分会咬你

  正确心智:Runtime 是工具,不是信仰

  工具的选择标准:
    1. 业务负载特征(QPS / 长连接 / 流式)
    2. 团队能力(协程会不会写)
    3. 运维成本(K8s 熟不熟)
    4. 演进空间(将来要不要扩展)
    5. 风险偏好(稳定性 vs 性能)

  融合的本质:复杂度的转移

  单一 Runtime:
    代码层简单(一种范式)
    治理层简单(一种发布模式)
    ✅ 轻松
    ❌ 但天花板低

  融合 Runtime:
    代码层复杂(三种范式共存)
    治理层简单(统一基础设施)
    ❌ 学习成本高
    ✅ 但能 unlock 各场景最优

  大白话:融合架构不是"为了用而用",是"承认不同业务有不同特征"。中后台凑合用 FPM 就够了,你硬上 Swoole
  是给自己找麻烦。但对外网关用 FPM,你扛不住流量。承认差异,组合使用,这是工程师的成熟标志。

  ---
  四、第 2 阶:融合架构整体形态

  标准融合架构图

                      ┌───────────────────────┐
                      │    Ingress / API GW    │  ←Nginx / APISIX
                      └────────────┬───────────┘
                                   │
          ┌───────────────────────┼─────────────────────────┐
          ↓                       ↓                         ↓
     ┌─────────┐            ┌──────────┐              ┌───────────┐
     │   FPM    │            │FrankenPHP│              │  Swoole   │
     │ /admin   │            │ /api/*   │              │ /gateway  │
     │ 后台     │            │ Laravel  │              │  Hyperf   │
     │ Octane   │            │ + Octane │              │ + 协程    │
     │ off      │            │ Worker   │              │           │
     └────┬─────┘            └─────┬────┘              └─────┬─────┘
          │                         │                         │
          └─────────────────────────┼─────────────────────────┘
                                    ↓
          ┌─────────────────────────────────────────────┐
          │  共享基础设施(单一来源,所有 Runtime 共用)│
          │  - MySQL / PostgreSQL                       │
          │  - Redis(Session / Cache / Queue)         │
          │  - Kafka / RabbitMQ                         │
          │  - Object Storage(OSS / S3)               │
          └─────────────────────────────────────────────┘
                                    │
          ┌─────────────────────────┼─────────────────────────┐
          ↓                        ↓                        ↓
     ┌──────────┐            ┌──────────┐             ┌──────────┐
     │ 统一观测 │            │ 统一配置 │             │ 统一发布 │
     │ Prometheus│           │ Nacos /  │             │ ArgoCD   │
     │ Grafana  │            │ ConfigMap│             │ GitOps   │
     │ Jaeger   │            │ Vault    │             │ Helm     │
     └──────────┘            └──────────┘             └──────────┘

  三层关键认知

  1. 入口统一:对外是一个域名,用户感知不到背后多 Runtime
  2. 数据统一:Session、Cache、Data 走外部基础设施,可跨 Runtime 共享
  3. 治理统一:发布、监控、配置一套机制管所有 Runtime

  各 Runtime 的"职责分工"

  ┌─────────────────────┬─────────────────────┬───────────────────────────────┐
  │      业务场景       │       Runtime       │             原因              │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ 后台管理 / 内部工具 │ FPM                 │ QPS 低,改动多,稳定优先        │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ 对外 RESTful API    │ FrankenPHP          │ 中等 QPS,性能 5x,Laravel 友好 │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ 高并发网关 / 聚合层 │ Swoole + Hyperf     │ 协程并发,QPS 万级             │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ WebSocket / IM      │ Workerman 或 Swoole │ 长连接                        │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ AI 流式输出 / SSE   │ FrankenPHP          │ 原生 SSE 支持                 │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ 离线任务 / Cron     │ FPM CLI             │ 简单可靠                      │
  ├─────────────────────┼─────────────────────┼───────────────────────────────┤
  │ 消息队列消费        │ Swoole              │ 协程高并发                    │
  └─────────────────────┴─────────────────────┴───────────────────────────────┘

  大白话:融合架构的精髓是"让每个业务跑在最适合的 Runtime,但用一套基础设施粘合"。这就是现代 PHP
  部署的"标准答案"——你不会看到大厂全Swoole 或全 FPM,都是混着用。

  ---
  五、第 3 阶:三大 Runtime 深度剖析

  FPM:成熟稳定的"老兵"

  进程模型:
    Master + Worker pool
    每请求 fork-like(共享内存,内存隔离)
    请求结束,内存自动回收

  优势:
    ✅ 心智简单
    ✅ 代码无副作用
    ✅ 稳定运行 20 年
    ✅ 内存泄漏几乎不可能(请求级)
    ✅ 任何框架都能跑

  劣势:
    ❌ 每请求重新加载框架()
    ❌ 不支持长连接
    ❌ 不支持协程
    ❌ QPS 上限低(单核 200-500)

  FPM 适用场景

  ✅ 强烈推荐:
    - 中后台管理系统(QPS < 200)
    - 老项目维持运行
    - 团队不熟悉协程
    - 业务复杂、改动频繁

  ❌ 不推荐:
    - WebSocket
    - QPS > 1000 的接口
    - 长流式输出

  FrankenPHP:2024 后的"中庸之选"

  架构:
    Caddy(Go 高性能服务器)
    + PHP 嵌入式 SAPI
    + Worker 模式(可选)

  特性:
    ✅ Worker 模式(常驻内存,框架不重启)
    ✅ 自带 HTTPS / HTTP/3
    ✅ 原生 SSE 支持(AI 流式)
    ✅ Laravel Octane 一行接入
    ✅ 心智负担只有 Swoole 的 30%

  劣势:
    ⚠️协程能力弱(用 Fiber)
    ⚠️生态新,踩坑可能多
    ⚠️极高并发(万级)不如 Swoole

  FrankenPHP Worker 模式入门

  FROM dunglas/frankenphp:latest
  COPY . /app
  WORKDIR /app
  ENV FRANKENPHP_CONFIG="worker ./public/index.php"
  ENV SERVER_NAME=":80"

  // public/index.php(Laravel Octane 自动处理)
  ignore_user_abort(true);
  require __DIR__ . '/../vendor/autoload.php';
  $app = require __DIR__ . '/../bootstrap/app.php';

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

  for ($i = 0; $i < 1000; $i++) {
      $keep = \frankenphp_handle_request($handler);
      gc_collect_cycles();
      if (!$keep) break;
  }

  Swoole + Hyperf:性能天花板

  进程模型:
    Master(管控)
    Manager(管 Worker)
    Reactor ×N(IO 多路复用)
    Worker ×N(每个跑无数协程)
    TaskWorker ×M(同步重活)

  特性:
    ✅ QPS 单核可达 1000+
    ✅ 协程并发(单 Worker 万协程)
    ✅ 一键 hook IO(SWOOLE_HOOK_ALL)
    ✅ WebSocket / TCP / UDP 全支持

  劣势:
    ❌ 单例污染、static 共享(用户串号)
    ❌ 内存泄漏需要 max_request 兜底
    ❌ CPU 密集任务卡死整个 Worker
    ❌ 心智负担最高

  Swoole 三大铁律(必须记)

  1. 不写 static / 单例   →用 Coroutine::getContext()
  2. 永远 max_request    →防内存泄漏
  3. CPU 密集走 TaskWorker →别污染协程 Worker

  三 Runtime 综合对比

  ┌──────────────┬───────────────┬───────────────┬──────────────┐
  │     维度     │      FPM      │  FrankenPHP   │    Swoole    │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 单核 QPS     │ 200-500       │ 2000-5000     │ 5000-20000   │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 内存(每实例)30-50MB       │ 100-200MB     │ 200-500MB    │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 启动开销     │ 每请求        │ 0(常驻)0(常驻)      │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 协程         │ ❌            │ Fiber()     │ ✅           │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ WebSocket    │ ❌            │ ✅(实验)      │ ✅(主战场)   │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ HTTP/3       │ 看 Web 服务器 │ ✅(原生)      │ ❌           │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 学习成本     │ 低            │ 中            │ 高           │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 运维复杂度   │ 低            │ 中            │ 中高         │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 生态成熟     │ 极成熟        │ 中等(2024) │ 成熟         │
  ├──────────────┼───────────────┼───────────────┼──────────────┤
  │ 故障概率     │ 低            │ 中            │ 中(用错就崩) │
  └──────────────┴───────────────┴───────────────┴──────────────┘

  大白话:FPM = 老黄牛(稳但慢)FrankenPHP = 新派电动车(快、智能、新)Swoole = F1
  赛车(快但要专业司机)。绝大多数公司:FrankenPHP 接 80% 业务,FPM 守稳定后台,Swoole
  顶高并发场景——这就是融合架构的"黄金比例"。

  ---
  六、第 4 阶:Runtime 选型决策树(精确到接口级)

  决策树(直接照查)

  新接口要上线了,选哪个 Runtime?

  Q1:有 WebSocket / TCP 长连接吗?
     ├─ 是 →Q1.1:并发多大?
     │       ├─ < 1 万连接 →Workerman
     │       └─ > 1 万连接 →Swoole
     └─ 否 →Q2

  Q2:有 SSE / 流式输出吗?(AI 应用)
     ├─ 是 →FrankenPHP
     └─ 否 →Q3

  Q3:QPS 预估多大?
     ├─ < 200 →Q3.1:复杂后台?
     │           ├─ 是 →FPM(稳定优先)
     │           └─ 否 →FrankenPHP(顺手)
     ├─ 200-2000 →FrankenPHP
     ├─ 2000-10000 →Q3.2:有协程经验?
     │              ├─ 是 →Swoole + Hyperf
     │              └─ 否 →FrankenPHP(横向扩)
     └─ > 10000 →Swoole + Hyperf(必须)

  Q4:接口里有大量并发外部调用吗?(聚合层)
     └─ 是 →Swoole(协程并发救命)

  Q5:计算密集任务?
     └─ 是 →跑 CLI / TaskWorker 隔离

  真实业务场景对应

  # 一个电商系统的典型分布

  后台管理:                FPM           # 5 个开发用,QPS 50
  商家中心:                FPM           # 商家用,QPS 200

  C 端用户中心:            FrankenPHP    # 用户登录、个人中心,QPS 800
  商品详情 API:            FrankenPHP    # QPS 3000
  订单创建 API:            FrankenPHP    # QPS 1500

  API 网关:                Swoole+Hyperf # 聚合下游 5 个服务,QPS 8000
  搜索 API:                Swoole+Hyperf # 高并发,QPS 5000
  推荐 API:                Swoole+Hyperf # 聚合多源,QPS 3000

  IM 服务:                 Swoole/Workerman # WebSocket
  推送服务:                Workerman        # 长连接

  AI 助手(流式):          FrankenPHP    # SSE 输出
  报表生成(异步):         FPM CLI       # 跑长任务

  定时任务:               FPM CLI       # Cron
  消息消费(高频):        Swoole       # 协程并发
  消息消费(低频):        FPM CLI       # 简单

  选型矩阵(团队决策用)

  做选型时同时评估:

    业务 QPS    │  10100 │ 1k  │ 10k │ 100k│
    ─────────────┼─────┼─────┼─────┼─────┼─────┤
    FPM         │  ✅  │  ✅  │  🟡   │  ❌  │  ❌  │
    FrankenPHP │  ✅  │  ✅  │  ✅  │  🟡   │  ❌  │
    Swoole      │  🟡   │  🟡   │  ✅  │  ✅  │  ✅  │

    ✅ = 推荐,🟡  = 可用,❌ = 不合适

  大白话:选型不是凭爱好,是凭场景。先问"接口长啥样",再问"团队会啥",最后问"运维能扛吗"——三个答案对齐才能做决策。最大的反模
  式是"我们公司就用 X"——这是一刀切,不是选型。

  ---
  七、第 5 阶:边界协议(让多 Runtime 协作)

  共享数据的三大原则

  1. Session 必须外置
     FPM、Franken、Swoole 之间必须共享 →Redis

  2. Cache 必须外置
     - Redis 单一来源
     - 各 Runtime 用同样的 key 规范

  3. 配置必须外置
     - Nacos / ConfigMap
     - 别在代码里写死

  Session 共享配置(关键)

  // config/session.php(三 Runtime 都用这套)
  return [
      'driver'    => 'redis',
      'connection'=> 'session',
      'lifetime'  => 7200,
      'cookie'    => 'sid',
      'domain'    => '.acme.com',           // 跨子域名共享
      'secure'    => true,
      'http_only' => true,
      'same_site' => 'lax',
  ];

  // config/database.php
  'redis' => [
      'session' => [
          'host'     => env('REDIS_HOST'),
          'port'     => 6379,
          'database' => 1,                   // 专门的 Session DB
      ],
      'cache' => [
          'host'     => env('REDIS_HOST'),
          'database' => 2,
      ],
  ],

  Cookie 域共享(让 SSO 跨 Runtime)

  admin.acme.com    ←FPM,登录后种 .acme.com cookie
  api.acme.com      ←FrankenPHP,读到同一 cookie
  gateway.acme.com  ←Swoole,也能读到

  关键:Cookie domain = .acme.com(顶级域)

  网关分流策略(Nginx)

  # /etc/nginx/conf.d/main.conf

  upstream fpm_admin     { server fpm-admin:9000;     keepalive 32; }
  upstream franken_api   { server franken-api:8080;   keepalive 64; }
  upstream swoole_gw     { server swoole-gw:9501;     keepalive 128; }
  upstream workerman_ws  { server workerman-ws:8484;  keepalive 32; }

  map $host $backend {
      admin.acme.com    fpm_admin;
      api.acme.com      franken_api;
      gateway.acme.com  swoole_gw;
      ws.acme.com       workerman_ws;
  }

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

      # 主 API 走 FrankenPHP
      location /v1/ {
          proxy_pass http://franken_api;
          proxy_http_version 1.1;
          proxy_set_header Connection "";
      }

      # 高并发聚合走 Swoole
      location /v1/aggregate/ {
          proxy_pass http://swoole_gw;
      }

      # 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;
      }

      # 后台直接走 FPM
      location /admin {
          proxy_pass http://fpm_admin;
      }
  }

  APISIX 智能路由(更高级)

  routes:
    - uri: /api/users/*
      upstream:
        type: roundrobin
        discovery_type: nacos
        service_name: user-service-franken
      plugins:
        jwt-auth: {}
        limit-req: { rate: 1000, burst: 200 }

    - uri: /api/aggregate/*
      upstream:
        service_name: aggregate-service-swoole
      plugins:
        circuit-breaker: { unhealthy: { failures: 5 } }

    - uri: /admin/*
      upstream:
        service_name: admin-service-fpm
      plugins:
        ip-restriction: { whitelist: ["10.0.0.0/8"] }

  内部服务调用规范

  ✅ 走 HTTP / gRPC
    - FPM 服务调 Swoole 服务:HTTP
    - Swoole 服务调 FrankenPHP 服务:HTTP/gRPC
    - 都通过 Service Discovery(Nacos / K8s Service)

  ❌ 别用 RPC 框架的"私有协议"
    - 跨 Runtime 不兼容(如 Hyperf JSONRPC 只 Swoole 支持)

  共享 Session 的潜在坑

  坑 1:FPM 写完立刻 close,Swoole 协程读时延迟
    解药:FPM 端写完 session_write_close()2:Swoole 协程内 session 串号
    解药:Hyperf 用协程上下文,不要用全局 $_SESSION3:Redis 配置不一致
    解药:不同 Runtime 用同一个 Redis 配置(envFrom)

  大白话:让多 Runtime 协作的关键是"数据外置 + 协议标准化"。Session、Cache、Config 全部走外部基础设施,Runtime 之间只通
  HTTP/gRPC。做到了这点,你能在 5 分钟内把一个接口从 FPM 切到 FrankenPHP,业务无感。

  ---
  八、第 6 阶:K8s 中的多 Runtime 部署模式

  三种部署模式

  模式 A:多 Deployment 各跑各的(推荐)
    - 每种 Runtime 一个 Deployment
    - 各自独立扩缩容
    - 适合大多数公司

  模式 B:单 Pod 多容器(罕见)
    - Pod 内一个 FPM + 一个 Swoole sidecar
    - 适合迁移过渡期
    - 不推荐长期使用

  模式 C:同 Pod 多服务(混合)
    - Nginx + FPM + Swoole 在一 Pod
    - 适合资源受限环境
    - 不推荐(违反单一职责)

  模式 A:标准 K8s 部署(推荐)

  # deployments/fpm-admin.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: fpm-admin
    labels: { app: fpm-admin, runtime: fpm }
  spec:
    replicas: 3
    selector:
      matchLabels: { app: fpm-admin }
    template:
      metadata:
        labels: { app: fpm-admin, runtime: fpm }
      spec:
        containers:
          - name: app
            image: registry.acme.com/admin-fpm:abc123
            ports: [{ containerPort: 80 }]
            resources:
              requests: { cpu: 100m, memory: 128Mi }
              limits:   { cpu: 500m, memory: 512Mi }
            livenessProbe:
              httpGet: { path: /healthz, port: 80 }
            readinessProbe:
              httpGet: { path: /readyz, port: 80 }

  # deployments/franken-api.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: franken-api
    labels: { app: franken-api, runtime: frankenphp }
  spec:
    replicas: 5
    template:
      spec:
        containers:
          - name: app
            image: registry.acme.com/api-franken:abc123
            env:
              - name: FRANKENPHP_NUM_THREADS
                value: "16"
            resources:
              requests: { cpu: 500m, memory: 512Mi }
              limits:   { cpu: 2000m, memory: 2Gi }     # FrankenPHP 内存大些
            livenessProbe:
              httpGet: { path: /healthz, port: 80 }
            lifecycle:
              preStop:
                exec:
                  command: ["/bin/sh", "-c", "sleep 15 && kill -TERM 1"]

  # deployments/swoole-gateway.yaml
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: swoole-gateway
    labels: { app: swoole-gateway, runtime: swoole }
  spec:
    replicas: 4
    template:
      spec:
        containers:
          - name: app
            image: registry.acme.com/gateway-swoole:abc123
            env:
              - name: SWOOLE_WORKER_NUM
                value: "8"
              - name: SWOOLE_MAX_REQUEST
                value: "10000"
            resources:
              requests: { cpu: 1000m, memory: 1Gi }
              limits:   { cpu: 4000m, memory: 4Gi }     # Swoole CPU 密集型
            livenessProbe:
              tcpSocket: { port: 9501 }
              initialDelaySeconds: 30
            lifecycle:
              preStop:
                exec:
                  command: ["/bin/sh", "-c", "sleep 20 && kill -TERM 1"]

  不同 Runtime 的资源配置对照

  ┌────────────┬─────────────┬───────────┬─────────────┬───────────┐
  │  Runtime   │ CPU Request │ CPU Limit │ Mem Request │ Mem Limit │
  ├────────────┼─────────────┼───────────┼─────────────┼───────────┤
  │ FPM        │ 100m        │ 500m      │ 128Mi       │ 512Mi     │
  ├────────────┼─────────────┼───────────┼─────────────┼───────────┤
  │ FrankenPHP │ 500m        │ 2000m     │ 512Mi       │ 2Gi       │
  ├────────────┼─────────────┼───────────┼─────────────┼───────────┤
  │ Swoole     │ 1000m       │ 4000m     │ 1Gi         │ 4Gi       │
  ├────────────┼─────────────┼───────────┼─────────────┼───────────┤
  │ Workerman  │ 500m        │ 2000m     │ 512Mi       │ 2Gi       │
  └────────────┴─────────────┴───────────┴─────────────┴───────────┘

  核心规则:常驻 Runtime(Franken/Swoole/Workerman)资源给足,内存可能慢慢涨;FPM 进程级释放,内存可控。

  HPA 配置(每种 Runtime 不同策略)

  # FPM:CPU 主导(每请求重型,CPU 涨快)
  apiVersion: autoscaling/v2
  kind: HorizontalPodAutoscaler
  metadata: { name: fpm-admin-hpa }
  spec:
    scaleTargetRef:
      kind: Deployment
      name: fpm-admin
    minReplicas: 3
    maxReplicas: 30
    metrics:
      - type: Resource
        resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

  ---
  # FrankenPHP:CPU + 自定义 QPS 双指标
  apiVersion: autoscaling/v2
  kind: HorizontalPodAutoscaler
  metadata: { name: franken-api-hpa }
  spec:
    scaleTargetRef:
      kind: Deployment
      name: franken-api
    minReplicas: 5
    maxReplicas: 50
    metrics:
      - type: Resource
        resource: { name: cpu, target: { type: Utilization, averageUtilization: 60 } }
      - type: Pods
        pods:
          metric: { name: http_requests_per_second }
          target: { type: AverageValue, averageValue: "500" }

  ---
  # Swoole:协程数指标(更精准)
  apiVersion: autoscaling/v2
  kind: HorizontalPodAutoscaler
  metadata: { name: swoole-gateway-hpa }
  spec:
    scaleTargetRef:
      kind: Deployment
      name: swoole-gateway
    minReplicas: 4
    maxReplicas: 30
    metrics:
      - type: Pods
        pods:
          metric: { name: swoole_coroutines_active }
          target: { type: AverageValue, averageValue: "5000" }

  优雅停机的 Runtime 差异

  # FPM:Nginx + FPM 优雅退出
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - |
          sleep 15            # 等 K8s 摘 Service
          nginx -s quit       # Nginx 不接新请求,处理老请求
          sleep 30            # 等 PHP-FPM 处理完
          kill -QUIT $(cat /var/run/php-fpm.pid)

  # FrankenPHP:发 SIGTERM,内置优雅退出
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - |
          sleep 15
          kill -TERM 1        # FrankenPHP 收到 TERM 后会处理完手头请求

  # Swoole:发 SIGTERM,需要 worker 配合
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - |
          sleep 20            # 等长一些(Swoole 协程多)
          kill -TERM 1

  // Swoole worker.php 优雅退出
  $server->on('Shutdown', function () {
      // 等所有协程结束
      sleep(5);
  });

  不同 Runtime 的 readiness 差异

  // FPM /readyz
  Route::get('/readyz', function () {
      DB::select('SELECT 1');
      Redis::ping();
      return 'OK';
  });

  // FrankenPHP /readyz(Worker 模式,可加更多)
  Route::get('/readyz', function () {
      DB::select('SELECT 1');
      Redis::ping();

      // Octane warm 检查
      if (Octane::worker()?->isReady()) {
          return 'READY';
      }
      return response('NOT READY', 503);
  });

  // Swoole /readyz
  $server->on('Request', function ($req, $res) {
      if ($req->server['request_uri'] === '/readyz') {
          $stats = Coroutine::stats();
          if ($stats['coroutine_num'] > 9000) {
              // 协程接近上限,拒接新请求
              $res->status(503);
              return $res->end('OVERLOAD');
          }
          return $res->end('READY');
      }
  });

  大白话:多 Runtime 在 K8s 上跑,关键是"分别部署,统一调度"。每个 Runtime 一个
  Deployment,资源、HPA、停机各按自己的特性配。Runtime 不同,但 K8s 抽象层让它们对外像同一种东西——这就是云原生的魅力。

  ---
  九、第 7 阶:跨 Runtime 协作(共享 SDK / 协议)

  共享代码的策略

  方案 A:同一个项目仓库,不同入口
    - 一份 Laravel 代码
    - public/index.php(FPM)
    - public/index.octane.php(FrankenPHP)
    - 没有 Swoole 入口(Swoole 通常用 Hyperf)

  方案 B:多仓库 + 共享 SDK
    - 项目 A:Laravel + FrankenPHP
    - 项目 B:Hyperf + Swoole
    - 公共 SDK:Composer 包共享

  方案 C:单仓库 monorepo
    - apps/admin(FPM)
    - apps/api(FrankenPHP)
    - apps/gateway(Swoole)
    - packages/sdk(共用)

  推荐:Monorepo + Composer 私有源(最实用)

  acme-platform/
  ├── apps/
  │   ├── admin/                    ←Laravel 11 + FPM
  │   │   ├── composer.json
  │   │   └── ...
  │   ├── api/                      ←Laravel 11 + FrankenPHP
  │   │   ├── composer.json
  │   │   └── ...
  │   └── gateway/                  ←Hyperf + Swoole
  │       ├── composer.json
  │       └── ...
  └── packages/
      ├── shared-domain/            ←业务领域模型(三 Runtime 共用)
      ├── shared-sdk/               ←服务客户端 SDK
      └── shared-infra/             ←基础设施抽象(日志、监控)

  // apps/api/composer.json
  {
      "require": {
          "acme/shared-domain": "^1.0",
          "acme/shared-sdk":    "^1.0",
          "acme/shared-infra":  "^1.0"
      },
      "repositories": [
          { "type": "path", "url": "../../packages/*", "options": { "symlink": true } }
      ]
  }

  共享 SDK 必须做到"Runtime 中立"

  <?php
  // packages/shared-sdk/src/UserClient.php
  namespace Acme\Sdk;

  interface UserClient
  {
      public function getUser(int $id): ?array;
      public function isVip(int $id): bool;
  }

  // 三种 Runtime 各自实现
  // FPM 项目里:
  class GuzzleUserClient implements UserClient {
      public function getUser(int $id): ?array {
          return $this->client->get("/users/{$id}")->json();
      }
  }

  // Swoole 项目里:
  class CoroutineUserClient implements UserClient {
      public function getUser(int $id): ?array {
          return go(fn () => $this->client->get("/users/{$id}"));
      }
  }

  关键:UserClient 接口是抽象的,不绑 HTTP 客户端,各 Runtime 自己实现。

  共享领域代码的注意事项

  // ✅ 这种代码可以跨 Runtime 共用(纯逻辑)
  class OrderPriceCalculator {
      public function calc(array $items, string $level): int {
          $total = array_sum(...);
          if ($level === 'VIP') $total *= 0.9;
          return (int)$total;
      }
  }

  // ❌ 这种代码不要塞到共享包(Runtime 特定)
  class CoroutineOrderPlacer {        // 用了 go(),只能跑 Swoole
      public function place(array $data) {
          go(function () { /* ... */ });
      }
  }

  // ❌ 这种代码也不行(用了 Laravel 内部)
  use Illuminate\Support\Facades\Cache;
  class OrderCache { /* ... */ }

  跨 Runtime 服务调用的协议规范

  # 内部服务通信契约(必须先达成)
  protocol:
    default: HTTP/JSON over HTTPS
    high_qps: gRPC(可选,Hyperf 支持好)

  authentication:
    method: JWT(内部 Service Token)
    header: X-Service-Token

  trace:
    method: W3C Trace Context
    header: traceparent

  versioning:
    pattern: /v{N}/{resource}
    examples: /v2/users/123

  error_format:
    status_codes: HTTP standard
    body:
      code: business_error_code
      message: human readable
      request_id: trace_id

  跨 Runtime 调用示例(FrankenPHP API →Swoole Gateway)

  // FrankenPHP API 中调聚合层
  class OrderController {
      public function create(Request $req) {
          // 1. 准备 Trace 头
          $headers = [
              'traceparent' => Tracer::current()->headers()['traceparent'],
              'X-Service-Token' => $this->serviceTokenIssuer->issue('order-api'),
          ];

          // 2. 调 Swoole 网关聚合接口
          $resp = Http::withHeaders($headers)
              ->post('http://swoole-gateway/v1/aggregate/order-context', [
                  'user_id' => $req->user()->id,
                  'items' => $req->items,
              ]);

          // 3. 业务逻辑
          return $this->orderService->place($resp->json());
      }
  }

  // Swoole 网关聚合接口
  #[Controller("/v1/aggregate")]
  class AggregateController {
      public function orderContext(): array {
          $userId = $this->req->input('user_id');
          $items = $this->req->input('items');

          // 协程并发调多个下游
          $parallel = new Parallel();
          $parallel->add(fn () => $this->userSvc->profile($userId), 'user');
          $parallel->add(fn () => $this->memberSvc->level($userId), 'level');
          $parallel->add(fn () => $this->couponSvc->available($userId), 'coupons');
          $parallel->add(fn () => $this->inventory->check($items), 'stock');

          return $parallel->wait();    // 4 个并发,总耗时 = 最慢那个
      }
  }

  大白话:跨 Runtime 协作的关键是"接口抽象 + 协议标准 + Trace 透传"。别让 FPM 项目和 Swoole
  项目变成"两套世界"——共享业务领域代码、共享SDK 接口、共享 Trace 链路——这才是融合的真正含义。

  ---
  十、第 8 阶:统一可观测性

  多 Runtime 的可观测性挑战

  挑战 1:三 Runtime 行为模型不同
    - FPM:每请求独立,简单
    - Franken:常驻,需关心 Worker 状态
    - Swoole:协程数飙高是危险信号

  挑战 2:指标命名不一致
    - FPM 报 "fpm_active_workers"
    - Swoole 报 "swoole_coroutines"
    - 同一个事故俩名字看着像两个事

  挑战 3:Trace 跨 Runtime 链路
    - FPM 调 Swoole 必须传 traceparent
    - Swoole 协程内开 span 很容易丢

  解药:统一规范 + OpenTelemetry

  1. 统一指标命名

  // 所有 Runtime 都用同一套指标名
  class StandardMetrics {
      public const HTTP_DURATION_HISTOGRAM = 'http_request_duration_seconds';
      public const HTTP_TOTAL_COUNTER = 'http_requests_total';
      public const HTTP_ERRORS_COUNTER = 'http_errors_total';

      // Runtime 特化指标用前缀
      public const FPM_WORKERS_GAUGE = 'php_fpm_workers_active';
      public const FRANKEN_THREADS_GAUGE = 'frankenphp_threads_busy';
      public const SWOOLE_COROUTINES_GAUGE = 'swoole_coroutines_active';
      public const SWOOLE_MEMORY_GAUGE = 'swoole_worker_memory_bytes';
  }

  2. Runtime 特化指标埋点

  // FPM 端
  class FpmMetrics {
      public function collect(): array {
          $status = file_get_contents('http://127.0.0.1/fpm-status?json');
          $data = json_decode($status, true);
          return [
              'fpm_active_workers'  => $data['active processes'],
              'fpm_idle_workers'    => $data['idle processes'],
              'fpm_listen_queue'    => $data['listen queue'],
          ];
      }
  }

  // FrankenPHP 端(自带 metrics endpoint)
  // 直接抓 :2019/metrics

  // Swoole 端
  $server->on('Stats', function () use ($registry) {
      $stats = $server->stats();
      $coroStats = Coroutine::stats();

      $registry->gauge('swoole_coroutines_active')->set($coroStats['coroutine_num']);
      $registry->gauge('swoole_connections')->set($stats['connection_num']);
      $registry->gauge('swoole_worker_memory_bytes')->set(memory_get_usage(true));
  });

  3. OpenTelemetry 统一接入(三 Runtime 共用)

  <?php
  // packages/shared-infra/src/Tracer.php
  namespace Acme\Infra;

  use OpenTelemetry\API\Globals;
  use OpenTelemetry\SDK\Trace\TracerProvider;

  class TracerFactory
  {
      public static function create(string $serviceName, string $version): void
      {
          $resource = ResourceInfo::create(Attributes::create([
              'service.name'    => $serviceName,
              'service.version' => $version,
              'service.runtime' => self::detectRuntime(),     // 自动识别
          ]));

          $exporter = new SpanExporter(
              (new OtlpHttpTransportFactory())->create(
                  'http://otel-collector.monitoring.svc:4318/v1/traces',
                  'application/x-protobuf'
              )
          );

          $provider = TracerProvider::builder()
              ->addSpanProcessor(new BatchSpanProcessor($exporter))
              ->setResource($resource)
              ->build();

          Globals::registerInitializer(fn ($b) => $b->withTracerProvider($provider));
      }

      private static function detectRuntime(): string
      {
          if (extension_loaded('swoole') && getenv('SWOOLE_RUNTIME')) {
              return 'swoole';
          }
          if (function_exists('frankenphp_handle_request')) {
              return 'frankenphp';
          }
          return 'fpm';
      }
  }

  4. Trace 透传(关键)

  // 入口处:从 header 提取 trace context
  $ctx = TraceContextPropagator::getInstance()->extract(getallheaders());
  $span = $tracer->spanBuilder('http.handle')
      ->setParent($ctx)
      ->setAttribute('runtime', detectRuntime())
      ->startSpan();

  // 出口处:注入 trace context 到下游 header
  $headers = [];
  TraceContextPropagator::getInstance()->inject($headers);
  $client->request('POST', '/downstream', ['headers' => $headers]);

  5. Swoole 协程内的 trace(最容易丢)

  use Swoole\Coroutine;

  go(function () use ($parentSpan) {
      // ❌ 错:协程内拿不到父 span
      $childSpan = $tracer->spanBuilder('child')->startSpan();

      // ✅ 对:显式传父 context
      $childSpan = $tracer->spanBuilder('child')
          ->setParent($parentSpan->storeInContext(Context::getCurrent()))
          ->startSpan();

      // 业务...
      $childSpan->end();
  });

  6. 统一日志规范

  // 所有 Runtime 共用同一种 JSON 格式
  {
    "ts": "2026-06-16T12:00:00.123Z",
    "level": "info",
    "msg": "order_placed",
    "service": "api",
    "runtime": "frankenphp",          ←关键:标明 Runtime
    "version": "v1.2.3",
    "trace_id": "abc123",
    "span_id": "xyz",
    "user_id": 88,
    "order_id": 1001
  }

  // 日志 processor 自动加 runtime 字段
  $logger->pushProcessor(function ($record) {
      $record['extra']['runtime'] = TracerFactory::detectRuntime();
      $record['extra']['trace_id'] = TraceContext::current()?->traceId();
      return $record;
  });

  7. 统一大盘(分 Runtime 显示)

  ┌────────────────────────────────────────────────┐
  │  系统总览                                        │
  ├────────────────────────────────────────────────┤
  │  总 QPS:     12,500                            │
  │  P99:        185ms                             │
  │  错误率:     0.04%                             │
  ├────────────────────────────────────────────────┤
  │  按 Runtime 分布(用 service.runtime label):  │
  │                                                  │
  │  FPM    (admin):       50 QPS    P99 320ms    │
  │  Franken(api):       8000 QPS    P99 180ms    │
  │  Swoole (gateway):   4000 QPS    P99 95ms     │
  │  Workerman(im):     12000 conn   ─            │
  └────────────────────────────────────────────────┘

  8. 关键告警(每 Runtime 不同)

  # FPM 告警:Worker 池满
  - alert: FpmWorkerPoolFull
    expr: php_fpm_workers_idle == 0 AND php_fpm_listen_queue > 10

  # FrankenPHP 告警:线程不够
  - alert: FrankenThreadsBusy
    expr: frankenphp_threads_busy / frankenphp_threads_total > 0.9

  # Swoole 告警:协程数飙升
  - alert: SwooleCoroutinesHigh
    expr: swoole_coroutines_active > 8000

  # Swoole 告警:内存泄漏(单 Worker)
  - alert: SwooleMemoryGrowing
    expr: rate(swoole_worker_memory_bytes[10m]) > 1000000

  大白话:多 Runtime 最大的可观测性陷阱是"看似正常,实则不一样"。三种 Runtime 同样跑 200ms,FPM 是正常,Franken
  该警惕,Swoole 已经异常。统一指标 + Runtime 标签 + 各自基线——才能让一套监控扛三种Runtime。

  ---
  十一、第 9 阶:跨 Runtime 治理(配置 / 密钥 / 发布)

  配置统一

  # K8s ConfigMap(所有 Runtime 共用)
  apiVersion: v1
  kind: ConfigMap
  metadata: { name: app-config }
  data:
    app.env: production
    app.timezone: Asia/Shanghai
    db.host: mysql.acme.svc.cluster.local
    redis.host: redis.acme.svc.cluster.local
    log.level: info

  ---
  # 各 Runtime 共用同一份(注意版本一致)
  apiVersion: apps/v1
  kind: Deployment
  metadata: { name: fpm-admin }
  spec:
    template:
      spec:
        containers:
          - name: app
            envFrom: [{ configMapRef: { name: app-config } }]

  ---
  apiVersion: apps/v1
  kind: Deployment
  metadata: { name: franken-api }
  spec:
    template:
      spec:
        containers:
          - name: app
            envFrom: [{ configMapRef: { name: app-config } }]

  密钥统一(External Secrets Operator)

  apiVersion: external-secrets.io/v1beta1
  kind: ExternalSecret
  metadata: { name: app-secret }
  spec:
    refreshInterval: 1h
    secretStoreRef:
      kind: ClusterSecretStore
      name: vault-backend
    target: { name: app-secret }
    data:
      - secretKey: db_password
        remoteRef: { key: secret/app, property: db_password }
      - secretKey: jwt_secret
        remoteRef: { key: secret/app, property: jwt_secret }

  # 三 Runtime 都读这个 secret
  envFrom:
    - configMapRef: { name: app-config }
    - secretRef: { name: app-secret }

  镜像构建统一基础

  # Dockerfile.base ——所有 Runtime 镜像的基础
  FROM php:8.3-alpine AS base
  RUN apk add --no-cache nginx supervisor curl libzip libpng \
   && docker-php-ext-install pdo_mysql opcache bcmath sockets \
   && pecl install redis && docker-php-ext-enable redis

  COPY docker/php.ini /usr/local/etc/php/conf.d/zz-app.ini
  COPY composer.json composer.lock /app/
  WORKDIR /app
  RUN composer install --no-dev --no-scripts --no-autoloader

  # Dockerfile.fpm
  FROM base AS fpm
  COPY --from=base /app/vendor /app/vendor
  COPY . /app
  EXPOSE 80
  CMD ["php-fpm", "-F"]

  # Dockerfile.franken
  FROM dunglas/frankenphp:latest AS franken
  COPY --from=base /app/vendor /app/vendor
  COPY . /app
  ENV FRANKENPHP_CONFIG="worker /app/public/index.php"
  EXPOSE 80

  # Dockerfile.swoole
  FROM phpswoole/swoole:php8.3 AS swoole
  COPY --from=base /app/vendor /app/vendor
  COPY . /app
  EXPOSE 9501
  CMD ["php", "/app/server.php"]

  统一发布流水线

  # .github/workflows/multi-runtime-deploy.yml
  name: Multi-Runtime Deploy

  on:
    push: { branches: [main] }

  jobs:
    # ─── 阶段 1:统一质量门禁 ───
    quality:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
        - run: vendor/bin/phpstan analyse
        - run: vendor/bin/pest --min-coverage=70

    # ─── 阶段 2:并行构建三种镜像 ───
    build:
      needs: quality
      strategy:
        matrix:
          runtime: [fpm, franken, swoole]
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
        - uses: docker/build-push-action@v5
          with:
            file: Dockerfile.${{ matrix.runtime }}
            tags: registry.acme.com/${{ matrix.runtime }}:${{ github.sha }}
            push: true

    # ─── 阶段 3:统一部署 ───
    deploy:
      needs: build
      runs-on: ubuntu-latest
      steps:
        - run: |
            # 同时升级三个 Deployment
            for runtime in fpm franken swoole; do
              kubectl set image deployment/${runtime}-app \
                app=registry.acme.com/${runtime}:${{ github.sha }} \
                -n production
            done

    # ─── 阶段 4:健康验证(每 Runtime 都查) ───
    verify:
      needs: deploy
      runs-on: ubuntu-latest
      steps:
        - run: |
            for service in fpm-admin franken-api swoole-gateway; do
              kubectl rollout status deployment/${service} -n production --timeout=5m
            done

  跨 Runtime 灰度策略

  # 假设要发版,三 Runtime 一起灰度
  # Argo Rollouts 配置

  apiVersion: argoproj.io/v1alpha1
  kind: Rollout
  metadata: { name: franken-api }
  spec:
    strategy:
      canary:
        steps:
          - setWeight: 5
          - pause: { duration: 5m }
          - analysis:
              templates: [{ templateName: success-rate-multi-runtime }]

  ---
  apiVersion: argoproj.io/v1alpha1
  kind: AnalysisTemplate
  metadata: { name: success-rate-multi-runtime }
  spec:
    metrics:
      # 同时检查 FPM、Franken、Swoole 都没问题才往下走
      - name: franken-success
        successCondition: result[0] >= 0.99
        provider:
          prometheus:
            query: |
              sum(rate(http_requests_total{runtime="frankenphp",status!~"5.."}[5m]))
              / sum(rate(http_requests_total{runtime="frankenphp"}[5m]))

      - name: swoole-success
        successCondition: result[0] >= 0.99
        provider:
          prometheus:
            query: |
              sum(rate(http_requests_total{runtime="swoole",status!~"5.."}[5m]))
              / sum(rate(http_requests_total{runtime="swoole"}[5m]))

  统一回滚

  #!/bin/bash
  # scripts/rollback-all-runtimes.sh

  NS=${1:-production}

  echo "🔄 一键回滚所有 Runtime..."
  for service in fpm-admin franken-api swoole-gateway workerman-im; do
      echo "  Rolling back $service..."
      kubectl argo rollouts undo $service -n $NS &
  done
  wait

  echo "✅ 全部回滚完成"

  大白话:多 Runtime 的治理,核心是"基础设施统一,Runtime
  异化"。配置、密钥、发布、回滚都用同一套机制——让运维觉得自己只在管"一种系统",实际上下面跑着三种
  Runtime。这就是融合架构的优雅之处。

  ---
  十二、第 10 阶:演进路线(从单 Runtime 到融合)

  演进的三种典型起点

  起点 1:全栈 FPM(老项目)
    →加 FrankenPHP 做对外 API
    →后台保留 FPM
    →高并发场景再上 Swoole

  起点 2:全栈 Swoole(性能驱动)
    →把后台拆出去用 FPM(降低心智)
    →边缘场景考虑 FrankenPHP

  起点 3:从零开始(新项目)
    →默认 FrankenPHP
    →后台用 FPM
    →必要时局部上 Swoole

  起点 1:FPM 单体 →融合架构(最常见)

  M1:摸底 + 容器化
    - 现有 FPM 应用容器化
    - 上 K8s

  M2:统一基础设施
    - Session/Cache 外置 Redis
    - 配置外置 ConfigMap
    - 监控接入 Prometheus

  M3:第一个 FrankenPHP 服务
    - 选一个对外 API
    - Laravel Octane + FrankenPHP
    - 流量灰度

  M4:扩大 FrankenPHP
    - 把更多 API 切到 FrankenPHP
    - FPM 留做后台

  M5:引入 Swoole(可选)
    - 网关或聚合层用 Swoole
    - 跨 Runtime 调用打通

  M6:沉淀融合体系
    - 共享 SDK / 治理
    - 统一观测 / 发布

  起点 2:Swoole 单体 →融合架构

  痛点:全栈 Swoole 对后台来说成本太高
    - 后台代码改动多,Swoole 的全局污染坑
    - 后台 QPS 低,Swoole 的性能用不上
    - 心智负担让团队疲惫

  演进:
    M1:把"低 QPS、改动频繁"模块拆出来
         →后台、报表、内部工具

    M2:这些模块用 FPM 重构
         →心智回归简单

    M3:Swoole 留给"网关、聚合、IM"
         →真正需要协程的场景

    M4:对外 API 评估上 FrankenPHP
         →性价比高

  起点 3:新项目的"理想姿态"

  Day 1:
    - 默认 FrankenPHP(主战场)
    - 后台业务直接 FPM(简单)
    - 不要预留 Swoole(等真要时再上)

  Month 6:
    - 业务出现高 QPS 网关需求
    - 评估:Hyperf + Swoole vs Franken 横扩
    - 选其一上线

  Year 1+:
    - 三 Runtime 各司其职
    - 治理体系成熟

  演进过程中的"双轨期"

  关键阶段:
    老 FPM 服务和新 Franken 服务并存

  策略:
    1. 用网关分流(灰度)
    2. Session 共享(Redis)
    3. 数据库共享
    4. 业务逻辑共享(packages/shared-domain)

  风险点:
    - 双写期数据一致性
    - 行为差异(老代码 vs 新框架)
    - 性能突变(用户预期)

  反向演进案例(从 Swoole 退到 FPM)

  真实案例:某创业公司初期上 Swoole + Hyperf
    团队 3 人,业务简单

  3 个月后问题:
    - 单例污染导致用户串号(2 次事故)
    - 协程死锁导致服务挂(1)
    - 团队 Onboarding 慢(新人 1 个月才能改代码)

  决策:
    - 业务模块退到 Laravel + FPM
    - 网关保留 Swoole(那才是真需要)
    - 心智负担降 70%
    - 性能"够用就好"

  教训:
    Runtime 选型要匹配团队能力和业务实际需求
    超前选型 = 自杀

  融合架构的成熟度等级

  Level 0:全 FPM
    特征:简单,但天花板低
    适合:初创、内部系统

  Level 1:FPM + FrankenPHP
    特征:外网用 Franken 提速,内部用 FPM
    适合:中等规模产品

  Level 2:FPM + FrankenPHP + Swoole
    特征:三 Runtime 各司其职
    适合:复杂业务、高并发

  Level 3:Level 2 + 完整治理
    特征:统一观测、发布、配置
    适合:大型产品 / 平台

  Level 4:Level 3 + 自动化决策
    特征:HPA 智能、容量预测
    适合:头部互联网

  大白话:融合架构是演进出来的,不是设计出来的。从单一 Runtime
  起步,跟着业务长出第二种、第三种——每次新增都是因为"老的真扛不住了"。警惕反向情况:为了"看起来现代"硬上多
  Runtime,只会拖累团队。

  ---
  十三、最佳落地节奏(6 个月路线图)

  ┌─────────────┬────────────────────────┬───────────────────────────┬──────┐
  │    阶段     │          时间          │          干什么           │ 风险 │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 1-4 周   │ 容器化 + 基础设施      │ Docker / K8s / 共享 Redis │ 低   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 5-8 周   │ 第一个 FrankenPHP 服务 │ 选一个对外 API 试点       │ 中   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 9-12 周  │ 跨 Runtime 治理        │ 配置 / 密钥 / 监控统一    │ 中   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 13-16 周 │ 扩大 FrankenPHP        │ 多个 API 切过来           │ 中   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 17-20 周 │ 引入 Swoole            │ 选高并发场景              │ 高   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 第 21-24 周 │ 完善融合体系           │ 共享 SDK / 灰度 / 回滚    │ 中   │
  ├─────────────┼────────────────────────┼───────────────────────────┼──────┤
  │ 持续        │ 优化 + 演进            │ 跟踪新版本、迭代          │ 持续 │
  └─────────────┴────────────────────────┴───────────────────────────┴──────┘

  ---
  十四、9 条铁律

  1. 永远基于场景选 Runtime——不是基于信仰。
  2. 永远共享基础设施——Session/Cache/Confi必须跨 Runtime 共用。
  3. 永远统一观测——一套Prometheus + 一套 Trace。
  4. 永远 Trace 透传——跨Runtime 调用必须带 traceparent。
  5. 永远资源差异化——Swool给足资源,FPM 控制资源。
  6. 永远独立扩缩容——每Runtime 一个 HPA。
  7. 永远分别灰度——一个Runtime 出事不带崩另两个。
  8. 永远共享业务代码——领域模型不绑Runtime。
  9. 永远渐进演进——新增Runtime 要业务驱动,不是技术追逐。

  ---
  十五、最容易踩的 8 个坑

  1. 没共享 Session:用户在 FPM 登录,跳到 Franken 又要登。
  2. Trace 不透传:跨 Runtime 链路看不到全貌。
  3. Swoole 单例污染:用户串号,排查地狱级。
  4. 资源配置一刀切:FPM 配 4Gi 浪费,Swoole 配 256Mi 撑不住。
  5. HPA 用同样指标:Swoole 应该看协程数,FPM 看 CPU。
  6. 没分 Runtime 监控:三 Runtime 一起看,发现不了 Runtime 特异问题。
  7. 业务代码绑死框架:Hyperf 的代码搬不到 Laravel,白做。
  8. 过早引入多 Runtime:团队 5 人就搞三种,运维负担拖垮。

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

  1. Runtime 融合不是炫技,是"承认差异"的工程哲学——业务有差异,Runtim就该有差异。
  2. 融合架构的难点不在 Runtime,在治理——三个Runtime 容易,统一发布、监控、配置才难。
  3. 最高级的融合,是用户和工程师都感觉"只有一种 Runtime"——对用户,统一域名同等体验;对工程师,共享SDK 共同基础设施——多
  Runtime 在背后默默工作。

  照这套抄完,你能把一个 PHP 系统从「单一 Runtime 走到底」进化到「不同业务跑最适合的
  Runtime,统一治理观测,演进可控」。这就是 2026 年现代 PHP 部署的终态——也是PHP 在云原生时代继续保持竞争力的关键拼图。

  ---
  至此,这套**"PHP 企业级工程化"系列**又添一砖:

  21. 工程化度量体系
  22. 多 Runtime 融合工程 ←当前

  需要我针对某一阶(比如FrankenPHP Worker 模式深度调优、Swoole + Hyperf 网关层完整脚手架、跨 Runtime
  业务领域代码共享实战、多 Runtime 灰度发布的 Argo Rollouts 配置)再深挖,或继续展开新主题(比如PHP 在 AI
  时代的工程化、Serverless PHP 实践、PHP 大型平台的多租户工程化),随时说。

更多推荐