传统Laravel项目零改动迁移到FrankenPHP的完整流程
·
传统 Laravel 零改动迁移到 FrankenPHP 完整流程
> 先把最重要的一句话说在前面,免得你迁移到一半发现被坑:
▎ 「零改动」只在「经典模式」下成立。 经典模式 = 把 FrankenPHP 当成一个更快的 Nginx+PHP-FPM 替代品,你的 Laravel
▎ 代码一行都不用动。
▎
▎ 而「Worker 模式」(性能翻几倍那个)不是零改动——它需要装laravel/octane
▎ 这个官方包,并且你的代码得满足「无状态」要求(第 10 章讲过的那些泄漏陷阱)。
所以这篇分两段走:先零改动跑起来(经典模式),再可选地榨性能(worker 模式)。两条路我都给完整流程。
---
第一部分:真·零改动迁移(经典模式)
步骤 0:迁移前盘点你现在的架构
传统 Laravel 生产环境通常长这样:
浏览器 →Nginx(收 HTTP、发静态文件、反代)
→PHP-FPM(跑 PHP,通过 FastCGI 协议)
→public/index.php →Laravel
涉及的配置文件:
- nginx.conf / sites-available/xxx(Nginx 站点配置)
- php-fpm.conf / www.conf(FPM 进程池配置)
- php.ini
FrankenPHP 把 Nginx + PHP-FPM 两个东西合并成一个进程,所以上面这套全部不再需要,换成一个 Caddyfile(或者干脆零配置)。
步骤 1:装 FrankenPHP
FrankenPHP 官方发布的是单个静态二进制(第 12 章讲的静态链接 libphp 那条路的成品),里面已经内置了 PHP
解释器和常用扩展,你机器上甚至不需要装 PHP。
方式 A:直接下二进制(最省事)
# Linux x86_64
curl https://frankenphp.dev/install.sh | sh
sudo mv frankenphp /usr/local/bin/
# 验证:里面这个 PHP 是什么版本、带哪些扩展
frankenphp php-cli -v
frankenphp php-cli -m # 列出内置扩展,先确认你 Laravel 要的扩展都在
方式 B:Docker(生产推荐,环境最干净)
# Dockerfile ——这是迁移 Laravel 的标准官方镜像
FROM dunglas/frankenphp:latest
# 装 Laravel 常用的 PHP 扩展(官方镜像带了 install-php-extensions 工具)
RUN install-php-extensions \
pdo_mysql \
redis \
intl \
gd \
zip \
bcmath \
opcache \
pcntl
# 把你的 Laravel 项目拷进镜像默认的网站根目录
COPY . /app
WORKDIR /app
# 装依赖(生产参数)
RUN composer install --no-dev --optimize-autoloader
▎ 关键检查点:迁移前一定先用 frankenphp php-cli -m 或 install-php-extensions 把你 composer.json 里 ext-*
▎ 要求的扩展全配齐。Laravel
▎ 常见依赖:pdo_mysql/pdo_pgsql、mbstring、openssl、tokenizer、xml、ctype、json、bcmath、fileinfo、redis(如果用
▎ Redis)、intl、gd/imagick(如果处理图片)、pcntl(如果用队列 --timeout)。少装扩展是迁移最常见的翻车点。
步骤 2:让 FrankenPHP 指向 Laravel 的入口
Laravel 的 HTTP 入口永远是 public/index.php,所有请求都被 rewrite 到它(这就是 Laravel
的「前端控制器」模式)。FrankenPHP 内置了对这个模式的支持,一个命令零配置就能跑:
# 进到 Laravel 项目根目录
cd /path/to/your-laravel
# 一行启动,--root 指向 public 目录
frankenphp php-server --root public/
就这一行。它会:
- 在 :443(带自动 HTTPS)或开发时 :80 起一个服务器
- 自动把所有请求按 Laravel 的规则 rewrite 到 public/index.php
- 静态文件(public/ 下的 css/js/图片)直接由服务器高速返回,不走 PHP
打开浏览器访问,你的 Laravel 原封不动就跑起来了。代码零改动。
步骤 3:用 Caddyfile 做正经配置(生产)
php-server 命令适合快速验证,生产环境用 Caddyfile 更可控。一个标准的 Laravel Caddyfile 长这样,逐行解释:
# Caddyfile ——放在项目根目录
{
# 全局选项块
frankenphp # 启用 FrankenPHP 模块
# auto_https off # 本地开发想关 HTTPS 时取消注释
}
your-domain.com { # 你的域名;Caddy 会自动申请并续签 Let's Encrypt 证书
root * public/ # ★网站根目录指向 Laravel 的 public/
encode zstd br gzip # 自动压缩响应(zstd/brotli/gzip)
php_server # ★核心:把请求交给内置 PHP 处理,
# 自动处理 index.php rewrite、静态文件分流
}
逐行大白话:
- frankenphp:在全局块里打开 FrankenPHP。
- your-domain.com { }:一个站点块。Caddy 最大的福利——自动HTTPS:你写个真实域名,它自动去 Let's Encrypt
申请证书、自动续期,0 配置。传统 Nginx 你得手搓 certbot。
- root * public/:告诉服务器网站根是 public/(Laravel 的 web 根),其它目录(app/、vendor/、.env)天然不暴露,安全。
- encode ...:响应压缩,替代 Nginx 的 gzip on。
- php_server:一条指令顶 Nginx 一大段 location ~ \.php$ + fastcgi_pass。它内部等价于:
- 请求的是存在的静态文件 →直接返回
- 否则 →rewrite 到 index.php 交给 PHP 跑
启动:
frankenphp run # 它会自动找当前目录的 Caddyfile
# 或指定:
frankenphp run --config /path/to/Caddyfile
步骤 4:把 PHP-FPM 和 Nginx 的配置「翻译」过来
迁移时你旧配置里的设置要找到对应位置:
┌─────────────────────────────────────┬──────────────────┬────────────────────────────────────────────────────────┐
│ 你原来在哪配 │ 原来怎么写 │ FrankenPHP 里怎么配 │
├─────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ php.ini 的 │ │ 还是 php.ini。用 frankenphp run 时它读环境里的 │
│ memory_limit、upload_max_filesize │ php.ini │ php.ini;Docker 镜像里在 │
│ 等 │ │ $PHP_INI_DIR/php.ini。改法不变 │
├─────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ │ │ Caddyfile 里 request_body { max_size 20MB },或 │
│ Nginx client_max_body_size 20M │ nginx.conf │ php.ini 的 │
│ │ │ upload_max_filesize/post_max_size(两边取小) │
├─────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ FPM pm.max_children(进程数) │ http://www.conf │ Caddyfile 的 frankenphp { num_threads N },控制 PHP │
│ │ │ 线程数 │
├─────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ Nginx 静态文件缓存头 │ location ~* │ Caddyfile 的 header 指令,或交给 php_server 默认处理 │
│ │ \.(jpg|css)$ │ │
├─────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ Nginx 反代 / 负载均衡 │ upstream │ Caddy 的 reverse_proxy,但单机场景通常不需要了 │
└─────────────────────────────────────┴──────────────────┴────────────────────────────────────────────────────────┘
设置线程数的写法:
{
frankenphp {
num_threads 40 # 类比 FPM 的 pm.max_children,按内存和负载调
}
}
步骤 5:迁移验证清单(逐项打勾)
零改动不代表零验证。挨个确认:
# 1) 扩展齐不齐(最常翻车)
frankenphp php-cli -m | sort
# 对照 composer.json 里所有 "ext-xxx"
# 2) Laravel 的缓存先清,避免旧路径残留
php artisan config:clear && php artisan route:clear && php artisan view:clear
# 3) 生产优化缓存(这些和 FPM 时一模一样,不变)
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# 4) 文件权限(storage 和 bootstrap/cache 要可写)
chmod -R ug+rw storage bootstrap/cache
功能层面要手动点一遍的:
- ✅ 普通页面渲染(GET)
- ✅ 表单提交、文件上传(POST + multipart,验证 read_post 那条链路)
- ✅ Session / 登录态(Cookie,验证 read_cookies)
- ✅ 文件下载、大响应(验证 ub_write 流式输出)
- ✅ php artisan 命令照常能跑(CLI 不受影响)
- ✅ 队列 worker(php artisan queue:work)和定时任务(schedule:run)——这些是独立进程,迁移不影响它们,照旧用
supervisor/cron 跑
步骤 6:队列、定时任务、Horizon 怎么办
这是迁移时容易忽略的:FrankenPHP 只负责 HTTP 那一半。Laravel 的后台部分是独立的:
# 定时任务:crontab 不变,照旧
* * * * * cd /path/to/laravel && php artisan schedule:run >> /dev/null 2>&1
# 队列:supervisor 配置不变,照旧用 php-cli 跑
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
command=php /path/to/laravel/artisan queue:work --sleep=3 --tries=3
注意 php 这里如果系统没装 PHP,用 frankenphp php-cli /path/to/artisan queue:work。FrankenPHP 二进制本身就能当 php
命令用。
到这一步,经典模式迁移完成,代码零改动,已经能上生产。性能通常已经比 Nginx+FPM 略好(少了 FastCGI
协议开销),且运维简化(一个进程、自动 HTTPS)。
---
第二部分:榨性能(Worker 模式,非零改动但收益巨大)
如果你想要 FrankenPHP 那个「快 3-4 倍」的效果,上 worker 模式。这需要 Laravel Octane(官方包,专门适配
Swoole/RoadRunner/FrankenPHP)。
为什么 worker 模式快:回顾第 7 章
经典模式每个请求都要:require autoload →启动整个 Laravel 框架(注册几百个服务、解析路由、读配置)→处理 →
全部销毁。Laravel 框架启动本身就要几十毫秒,90% 的时间花在重复启动框架上。
Worker 模式:框架只启动一次常驻内存,之后每个请求复用,省掉那几十毫秒。
步骤 1:装 Octane
composer require laravel/octane
# 安装并选 FrankenPHP 作为 server
php artisan octane:install --server=frankenphp
这会装好 Octane 并配置成用 FrankenPHP 的 worker 模式。
步骤 2:启动
# 开发
php artisan octane:start --server=frankenphp --watch
# ↑改代码自动重载(第 11.5 节那个热重载)
# 生产
php artisan octane:start --server=frankenphp --workers=4 --max-requests=500
# --workers worker 线程数,一般 = CPU 核数
# --max-requests 每个 worker 处理 N 个请求后重启,防内存泄漏的安全阀
--max-requests 很重要:它是「定期重启 worker」的兜底机制,就算你代码有轻微泄漏,跑 500 个请求就重启清零,不至于 OOM。
步骤 3:代码体检(这是「非零改动」的核心工作)
Worker 模式下框架常驻,第 10 章讲的所有泄漏陷阱都要排查。针对 Laravel 具体化:
①静态属性 / 单例累积
// ☠️危险
class SomeService {
private static array $cache = []; // worker 模式下永不清空
}
排查:grep 你代码里的 static 属性容器、static $x = []。
②容器里被你手动绑定的「请求作用域」单例
// 如果你在 ServiceProvider 里这样绑过单例,且它持有了请求数据:
$this->app->singleton(CurrentTenant::class, ...); // 小心:会跨请求残留
Octane 提供了配置来每个请求后重置指定服务:
// config/octane.php
'warm' => [...], // 每个请求前预热的服务
'flush' => [...], // 每个请求后清空的服务 ←把有状态的放这
'listeners' => [
RequestReceived::class => [...],
RequestTerminated::class => [
FlushTemporaryContainerInstances::class, // Octane 自带的清理
],
],
③Octane 内存泄漏检测
Octane 自带工具帮你定位:
# 它会报告哪些请求导致内存持续增长
php artisan octane:start --server=frankenphp
# 配合监控 memory_get_usage,或用 --max-requests 兜底
④Laravel 官方已知要注意的点(Octane 文档明确列出):
- 不要用 $request 的全局 helper 缓存:用 Auth::user() 等没问题(Octane 会重置),但你自己 static 缓存了 request()
就会串台。
- 配置/容器单例里别存请求数据。
- 第三方包:确认你用的包兼容 Octane(大部分主流包已兼容)。
步骤 4:迁移决策——到底用不用worker 模式
┌──────────┬──────────────────────┬──────────────────────────────┐
│ 维度 │ 经典模式 │ Worker 模式(Octane) │
├──────────┼──────────────────────┼──────────────────────────────┤
│ 代码改动 │ 零 │ 需排查无状态,可能改少量代码 │
├──────────┼──────────────────────┼──────────────────────────────┤
│ 性能 │ 比 FPM 略好 │ 比 FPM 快 3-4 倍 │
├──────────┼──────────────────────┼──────────────────────────────┤
│ 迁移风险 │ 极低 │ 中(状态泄漏风险) │
├──────────┼──────────────────────┼──────────────────────────────┤
│ 适合 │ 想平滑替换 Nginx+FPM │ 高并发、追求极致吞吐 │
└──────────┴──────────────────────┴──────────────────────────────┘
我的建议:分两步走。先经典模式零改动上生产、稳住(享受简化运维和自动 HTTPS),跑一段时间确认无误;之后再单独立项做
Octane 改造,在 staging
环境充分压测和泄漏排查后再切。别一步到位把「换服务器」和「改架构」两件事混在一次上线里——出问题难定位。
---
完整迁移路线图(一张图收尾)
现状:Nginx + PHP-FPM + Laravel
│
├─【第一阶段:零改动】───────────────────────────
│ 1. 装 FrankenPHP(二进制 or Docker dunglas/frankenphp)
│ 2. 用 install-php-extensions 配齐扩展(对照 composer.json)★最易翻车
│ 3. 写 Caddyfile:root →public/,php_server 一行搞定
│ 4. 把 php.ini / FPM / Nginx 设置翻译过来(见对照表)
│ 5. artisan config:cache / route:cache(和原来一样)
│ 6. 队列/定时任务用 frankenphp php-cli 跑,supervisor/cron 不变
│ 7. 功能清单逐项验证(POST/Session/上传/下载)
│ →上生产。代码零改动,运维简化,自动 HTTPS,性能略升
│
└─【第二阶段:可选提速】──────────────────────────
1. composer require laravel/octane
2. artisan octane:install --server=frankenphp
3. ★体检无状态:static属性、请求作用域单例、连接事务状态
4. config/octane.php 配 flush 清理有状态服务
5. octane:start --workers=N --max-requests=500(兜底防泄漏)
6. staging 压测 + 内存监控,确认无泄漏
→切生产。性能 3-4 倍
三句话记住:
1. 经典模式 = 真零改动,root public/ + php_server 一行,把它当更好用的 Nginx+FPM。
2. 迁移最大的坑是扩展没配齐,先 frankenphp php-cli -m 对照 composer.json。
3. 要极致性能上 Octane worker 模式,但那不是零改动——得排查第10 章那些跨请求状态泄漏,用 --max-requests 兜底。
更多推荐
所有评论(0)