《PHP多运行时选型与混合部署架构:FPM/Swoole/FrankenPHP/Workerman 的场景边界与性能模型》
·
PHP 多运行时选型与混合部署架构:FPM / Swoole / FrankenPHP / Workerman 的场景边界与性能模型
下面把这件事拆成 9 个台阶,每一阶给「干什么」+「完整代码/配置」+「大白话」。目标:让你看完知道每种运行时是什么、什么时 候选哪个、怎么混着部署。
---
一、先讲清动机(为什么 PHP 突然有这么多运行时)
2010 年 2020 年至今
───────────────────────── ─────────────────────────
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
协程池与连接池设计)再深挖,随时说。
更多推荐


所有评论(0)