编程 Laravel Octane 2026 选型:8 核 FrankenPHP 压出 8035 RPS,缺一个索引掉到 556

2026-09-15 21:31:05

Laravel Octane 2026 选型:8 核 FrankenPHP 压出 8035 RPS,缺一个索引掉到 556

相关地址:FrankenPHPFrankenPHP GitHubLaravel Octane 文档RoadRunnerSwoole

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 解释器

FrankenPHPGo 写的二进制,基于 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:

场景RPSavgp50p90p99
/bench/hello(纯 JSON)777812.32 ms12.04 ms16.08 ms21.38 ms
/json747412.81 ms20.42 ms
/compute(CPU 密集)325530.13 ms38.02 ms
/bench/db(主键点查)539617.93 ms23.28 ms
/bench/db-noindex(未命中索引)549175.80 ms228.20 ms
/bench/db-join(连表)494319.53 ms28.91 ms
Redis3078 ~ 5821
/bench/mixed(混合业务)606116.36 ms21.37 ms

并发 500:

场景RPSavgp50p90p99
/bench/hello(纯 JSON)803561.51 ms76.83 ms
/json767665.86 ms
/compute(CPU 密集)3152156.24 ms
/bench/db(主键点查)537594.54 ms109.53 ms
/bench/db-noindex(未命中索引)556871.25 ms1.02 s
/bench/db-join(连表)4627109.11 ms
Redis570189.19 ms
/bench/mixed(混合业务)499388.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
纯 JSON80351.73 亿~1150 万
Redis 业务57001.23 亿~820 万
MySQL 主键点查53751.16 亿~770 万
连表查询46271.00 亿~670 万
CPU 密集31526800 万~450 万
未命中索引5561200 万~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 持久化策略要压测验证。

推荐文章

程序员茄子在线接单