Laravel Octane 2026 选型:8 核 FrankenPHP 压出 8035 RPS,缺一个索引掉到 556
相关地址:FrankenPHP|FrankenPHP GitHub|Laravel Octane 文档|RoadRunner|Swoole
Octane 省下的到底是什么
PHP-FPM 每来一个请求就从零引导框架,boot 开销约 10–30 ms/请求,装了 Filament/Nova 更贵,请求结束全部丢弃。Octane 让每个 worker 引导一次常驻内存,后续请求复用已经构建好的框架,boot 税消失(首个请求除外)。
它不会让数据库查询变快,不缓存响应,也不并行化你的代码(Swoole 协程除外)。如果一个端点在 MySQL 里花 200 ms,Octane 只省掉其中 20 ms 的 boot。
composer require laravel/octane
php artisan octane:install --server=frankenphp
--server 可选 frankenphp / swoole / roadrunner。文档里出现 FrankenPHP、Swoole、Open Swoole、RoadRunner 四个名字,Open Swoole 是社区 fork,实际上就是三种架构。
三种架构:同进程、进程池 + RPC、协程
FrankenPHP:Go 二进制里嵌 PHP 解释器
FrankenPHP 是 Go 写的二进制,基于 Caddy,官方 PHP 解释器通过 cgo 直接内嵌在同进程里。没有代理跳转,没有进程间通信:Caddy 收到请求交给同进程的 PHP 线程,已引导的 Laravel 应用在那儿等着。
Caddy 本身就是 web server,HTTP/2、HTTP/3、自动 HTTPS 自带,前面不需要 Nginx。Dockerfile 很短:
FROM dunglas/frankenphp
COPY . /app
WORKDIR /app
RUN composer install --no-dev --optimize-autoloader
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
单容器、单端口,无 Nginx 无 supervisor。需要自定义时:
php artisan octane:start --server=frankenphp --caddyfile=/path/to/Caddyfile
取舍:三个里最年轻(2023 年末发布)。有托管商报告 18 个月里撞到两次内存泄漏回归,上游已修。配置是 Caddyfile 语法而不是 Nginx。HTTP/1.0 工具压测下表现平平。
Swoole:PECL 扩展,用协程换掉 PHP 执行模型
Swoole 是 PECL C 扩展,用事件循环 + 协程替换 PHP 执行模型:阻塞 I/O 时挂起当前协程,在同一个 worker 里跑另一个请求。这是另外两个没有的能力,也最挑代码。
Octane::concurrently([
fn () => Order::recent()->get(),
fn () => Cache::get('dashboard:stats'),
fn () => Feature::all(),
]);
三个操作并发执行,用 --task-workers 调整池大小。concurrently()、ticks、intervals、Swoole 版 Octane cache 和 tables 都是 Swoole / Open Swoole 独有。
门槛:扩展会冲突,部分包在协程里行为异常,Xdebug 不兼容(要用 Blackfire/Tideways);它是 PECL 编译扩展而非自动下载的二进制,Docker/CI 更定制;内存涨得快,生产环境 max_requests 通常设约 250(对比 RoadRunner 是 500)。
RoadRunner:Go server 管一池普通 PHP 进程
RoadRunner 是独立的 Go server 二进制,管理一池普通 PHP 进程,通过快速 RPC 转发请求。worker 是普通进程,有真实隔离:某个 worker 状态损坏或死掉,Go server 重启它,其他不受影响。
2018 年至今,插件生态最成熟(队列、gRPC、kv)。2026 年 4 月的基准里比 PHP-FPM 吞吐高 111.6%、p99 低 41%。
composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
php artisan octane:start --server=roadrunner
代价:没有 HTTP/3 和自动 HTTPS(需前置 Nginx/Caddy),没有协程。
8 核 FrankenPHP 实测环境
20 核物理机用 taskset -c 0-7 绑定 8 核,模拟 8 核云主机;FrankenPHP + Laravel Octane,Worker = 8,1:1 匹配 CPU 核心;压测工具 wrk 绑在 8-15 核物理隔离;生产模式 config:cache / route:cache / view:cache 全开;MySQL users 表 10 万行,Redis 真实连接;连表是 student(19261 行)JOIN school(429 行),两张都走主键索引。每个场景跑 20s × 3 取中位数。
并发 100:
| 场景 | RPS | avg | p50 | p90 | p99 |
|---|---|---|---|---|---|
| /bench/hello(纯 JSON) | 7778 | 12.32 ms | 12.04 ms | 16.08 ms | 21.38 ms |
| /json | 7474 | 12.81 ms | — | — | 20.42 ms |
| /compute(CPU 密集) | 3255 | 30.13 ms | — | — | 38.02 ms |
| /bench/db(主键点查) | 5396 | 17.93 ms | — | — | 23.28 ms |
| /bench/db-noindex(未命中索引) | 549 | 175.80 ms | — | — | 228.20 ms |
| /bench/db-join(连表) | 4943 | 19.53 ms | — | — | 28.91 ms |
| Redis | 3078 ~ 5821 | — | — | — | — |
| /bench/mixed(混合业务) | 6061 | 16.36 ms | — | — | 21.37 ms |
并发 500:
| 场景 | RPS | avg | p50 | p90 | p99 |
|---|---|---|---|---|---|
| /bench/hello(纯 JSON) | 8035 | 61.51 ms | — | — | 76.83 ms |
| /json | 7676 | 65.86 ms | — | — | — |
| /compute(CPU 密集) | 3152 | 156.24 ms | — | — | — |
| /bench/db(主键点查) | 5375 | 94.54 ms | — | — | 109.53 ms |
| /bench/db-noindex(未命中索引) | 556 | 871.25 ms | — | — | 1.02 s |
| /bench/db-join(连表) | 4627 | 109.11 ms | — | — | — |
| Redis | 5701 | 89.19 ms | — | — | — |
| /bench/mixed(混合业务) | 4993 | 88.01 ms | — | — | — |
数据里的几个硬结论
- 纯 API 摸到 8000 RPS。
- 真实 DB 点查稳在 5400 RPS;连表 4900 RPS,相比单表只降 9%。
- 主键点查 5396 vs 全表扫描 549:少一个索引,吞吐除以 10、延迟乘 10。c=500 时无索引 p99 破 1 秒,8 个 Worker 全被全表扫描阻塞。
- c=500 相比 c=100 吞吐几乎没涨(7778 → 8035),avg 从 12 ms 涨到 61 ms,8 个 Worker 已到物理极限。
- Redis 场景 3078~5821 大幅波动,原因是 Redis 开了 RDB 快照 / AOF 重写导致 fsync 阻塞。把
appendfsync改成no,或者把重写时间与业务高峰错开。
容量估算
按峰均比 4、单用户日均 15 次请求换算。纯 JSON 的 8035 峰值对应日均约 2009 QPS:
| 场景 | 峰值 RPS | 日 PV | 估算 DAU |
|---|---|---|---|
| 纯 JSON | 8035 | 1.73 亿 | ~1150 万 |
| Redis 业务 | 5700 | 1.23 亿 | ~820 万 |
| MySQL 主键点查 | 5375 | 1.16 亿 | ~770 万 |
| 连表查询 | 4627 | 1.00 亿 | ~670 万 |
| CPU 密集 | 3152 | 6800 万 | ~450 万 |
| 未命中索引 | 556 | 1200 万 | ~80 万 |
为什么基准互相矛盾
2026 年 4 月有份基准把 Swoole 压到 PHP-FPM 之下:它用 ab(HTTP/1.0 短连接)压轻 I/O 路由。Swoole 的优势全在并发 I/O 调度,几乎没有 I/O 等待时,协程调度纯属开销;FrankenPHP 的 HTTP/2 多路复用也没被 HTTP/1.0 触发。而报告 Swoole 最快的商家跑的是上游调用多、数据库扇出的 API,大量用 concurrently()。同一台服务器得出相反结论,两个测量都是诚实的。
选型
- 容器化、要最简栈:FrankenPHP。Cloud Run / K8s / Docker / VPS 上不想维护 Nginx 就用它,新手默认也合适。
- 迁移大型既有应用、稳定性优先:RoadRunner。进程隔离让失败模式最软。
- 有大量并发上游调用、真实扇出,团队能审协程安全:Swoole。
- profiler 显示 boot 不是瓶颈:三个都不用。300 ms 的端点里 260 ms 花在查询,Octane 没意义。
通用坑与部署
应用跨请求常驻内存,静态属性、singleton、memoize 的值会留到下一个请求。经典 bug 是某个包把认证用户缓存在 singleton 里,请求 N+1 看到的是请求 N 的用户。审计 singleton,用 Octane 的 flush 钩子,拿两个不同登录用户打同一个 worker 测试。
内存会涨,用 --max-requests 让 worker 回收,5000~10000 是常用区间;Swoole 生产环境约 250。
php artisan octane:reload
零停机换 worker,放进部署脚本。
每个 worker 一次只处理一个请求(Swoole 协程除外),慢请求占住 worker 槽,慢活儿扔队列。
把 EXPLAIN 进 CI,rows > 1000 阻塞合并。Worker 数 = CPU 核心数。监控看 p99,不看平均。Redis 持久化策略要压测验证。