定位
FrankenPHP 是用 Go 写、内嵌 Caddy 的 PHP 应用服务器。它把 PHP 编译进一个常驻进程,对外提供 HTTP/2、HTTP/3、自动 HTTPS。
它不是框架,跟 Laravel 是配合关系。
最有价值的用法:frankenphp run 直接跑一个 Laravel/ThinkPHP 项目,不改一行业务代码,就把「每请求启动一次 PHP」变成「常驻 worker」。
安装
官方二进制(最简单)
curl https://frankenphp.dev/install.sh | sh
frankenphp version
Docker
FROM dunglas/frankenphp
COPY . /app/public
docker run -p 80:80 -p 443:443 -v $PWD:/app/public dunglas/frankenphp
两种运行模式
模式一:经典模式(兼容 FPM)
frankenphp php-server -r public/
每次请求都是一个全新的 PHP 执行环境,行为和 PHP-FPM 一致。迁移零风险,但拿不到常驻内存的好处。
模式二:Worker 模式(关键特性)
frankenphp php-server --worker public/index.php -r public/
public/index.php 只执行一次,然后循环处理请求:
<?php
// worker 脚本的固定结构(Laravel 的 octane 风格)
$handler = static function () {
// 这段每次请求都执行
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
$kernel = $app->make(IlluminateContractsHttpKernel::class);
$response = $kernel->handle($request = IlluminateHttpRequest::capture());
$response->send();
$kernel->terminate($request, $response);
};
// 关键:循环 + 判断是否退出
$maxRequests = (int) ($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; $nbRequests++) {
$keepRunning = frankenphp_handle_request($handler);
gc_collect_cycles(); // 主动回收,避免内存累积
if (!$keepRunning) break;
}
frankenphp_handle_request() 是核心函数:它阻塞等待下一个请求,执行回调,返回是否继续。
核心概念
1. Caddy 就是 Web 服务器
不需要 Nginx。Caddy 负责 TLS、HTTP/2/3、静态文件、反向代理,配置写在 Caddyfile:
example.com {
root * /app/public
encode zstd gzip
php_server {
worker /app/public/index.php
}
}
自动 HTTPS 是 Caddy 的招牌功能,配好域名就自动申请续期证书。
2. Worker 模式的核心收益
| FPM | FrankenPHP Worker | |
|---|---|---|
| 每请求初始化框架 | 是 | 否(只初始化一次) |
| 单机 QPS(中等项目) | 基准 | 2-4 倍 |
| 内存 | 按进程数 | 常驻,比较稳定 |
收益来源很单纯:省掉了 autoload、容器启动、配置加载这些每请求都要做一遍的事。
3. 和 Swoole 的区别
| FrankenPHP | Swoole | |
|---|---|---|
| 实现语言 | Go | C 扩展 |
| 并发模型 | Go 的 goroutine | PHP 协程 |
| 业务代码要求 | 不用改(worker 模式下注意状态残留) | 必须用协程化客户端 |
| 部署形态 | 单进程内置 Web 服务器 | 通常配合 Nginx |
FrankenPHP 的门槛低得多:不用改代码、不用换数据库客户端,只是启动方式变了。
常见坑
① Worker 模式下的状态残留
和所有常驻内存方案一样:静态变量、全局单例会在请求间残留。Laravel 的 config、route 缓存是安全的,但你自己写的单例要注意。
② 内存泄漏
worker 会一直跑。内存缓慢上涨时,用 MAX_REQUESTS 定期重启 worker:
fastcgi / MAX_REQUESTS 500
③ 文件上传和 $_SERVER 的行为差异
worker 模式下 $_SERVER 来自第一个请求之后的状态,某些字段可能不准。用框架的 Request 对象而不是超全局变量。
④ 生态还在早期
版本 1.13,文档和案例都在积累。遇到问题可参考的资料远少于 Nginx + FPM。
什么时候选它
- 想拿常驻内存的性能,但不想改业务代码
- 想少运维一个组件(不需要 Nginx)
- 单机或小规模部署,想要自动 HTTPS
- 已经在用 Caddy
不选它的场景:需要成熟稳定的生产方案(Nginx + FPM 更保险)、需要复杂的反向代理和负载均衡(这些还是 Nginx 强)、团队不愿意试新东西。
一个务实的迁移路径
- 先用经典模式跑起来(
frankenphp php-server),确认功能正常 - 压力测试对比,看 worker 模式能带来多少提升
- 开 worker 模式,配
MAX_REQUESTS防内存泄漏 - 观察一周,没问题再替换生产环境
不要一步到位。 先用经典模式验证兼容性,是风险最低的做法。