8 核 FrankenPHP 下的 Laravel Octane:压测报告
一、为什么要做这次压测?
FrankenPHP 出来有一阵子了,社区里“跑分”不少,但大多是 Hello World。我一直想知道:
在真实生产模拟下(带 DB、带 Redis、带连表),它到底能扛多少?
“加个索引”和“不加索引”在 Octane 常驻内存模式下,代价量化到底是多少?
8 个 Worker 是不是上限?并发从 100 涨到 500,吞吐还能涨吗?
带着这三个问题,我搭了一套完全模拟生产的 8 核环境,硬核绑核压测。数据出来了,结论非常有意思。
二、测试环境(“实验室”条件)
为了避免“跑分作弊”,我刻意模拟了真实线上部署。
物理硬件:20 核物理机(但通过
taskset -c 0-7绑定 8 核,模拟标准 8 核云主机)服务端:FrankenPHP + Laravel Octane
Worker 数量:
8(与 CPU 核心数 1:1 匹配)压测工具:
wrk绑定 8-15 核(与服务端物理隔离,避免互相干扰)缓存策略:生产模式(
config:cache、route:cache、view:cache全开)数据层:
MySQL:
users表 10 万行真实数据Redis:真实连接,模拟缓存读写
连表:
student(19,261 行) JOINschool(429 行),均走主键索引
压测规则:每场景 20s × 3 次,取中位数
三、完整压测数据
1. 并发 100(c=100)
| 场景 | RPS | avg | p50 | p90 | p99 |
|---|---|---|---|---|---|
纯 JSON /bench/hello |
7778 | 12.32ms | 12.04 | 16.08 | 21.38 |
普通 JSON /json |
7474 | 12.81ms | 12.64 | 15.92 | 20.42 |
CPU 密集 /compute |
3255 | 30.13ms | 29.93 | 33.30 | 38.02 |
MySQL 主键点查 /bench/db |
5396 | 17.93ms | 17.68 | 19.65 | 23.28 |
未命中索引 /bench/db-noindex |
549 ⚠️ | 175.80ms | 175.18 | 199.48 | 228.20 |
连表查询 /bench/db-join |
4943* | 19.53ms | 18.96 | 21.40 | 28.91 |
Redis 操作 /bench/redis |
3078~5821 ⚠️ | 16.5ms | — | — | — |
混合业务 /bench/mixed |
6061* | 16.36ms | 16.15 | 18.00 | 21.37 |
2. 并发 500(c=500)
| 场景 | RPS | avg | p50 | p90 | p99 |
|---|---|---|---|---|---|
纯 JSON /bench/hello |
8035 | 61.51ms | 61.34 | 68.54 | 76.83 |
普通 JSON /json |
7676 | 65.86ms | 65.87 | 71.28 | 79.71 |
CPU 密集 /compute |
3152 | 156.24ms | 156.45 | 165.46 | 176.34 |
MySQL 主键点查 /bench/db |
5375* | 94.54ms | 94.16 | 99.36 | 109.53 |
未命中索引 /bench/db-noindex |
556 | 871.25ms | 887.75 | 935.68 | 1.02s |
连表查询 /bench/db-join |
4627 | 109.11ms | 106.98 | 123.85 | 145.61 |
Redis 操作 /bench/redis |
5701 ⚠️ | 89.19ms | — | — | — |
混合业务 /bench/mixed |
4993 ⚠️ | 88.01ms | 87.13 | 94.52 | 115.48 |
四、核心结论分析
🚀 1. 吞吐水平(8 核天花板)
纯 API 路由:摸到 8000 RPS,这是 Laravel 历史上从未有过的 PHP-FPM 时代成绩。
真实 DB 点查:稳在 5400 RPS,说明 ORM(Eloquent)在 Octane 常驻内存下,连接池和重复编译开销被压到了极低。
连表查询:达到 4900 RPS,相比单表仅下降 9%,证明主键索引 JOIN 在 MySQL 端并不是性能陷阱。
🔥 2. “不加索引”的量化代价(重点!)
这是本次压测最触目惊心的数据。
主键点查:
RPS = 5396,avg = 17.9ms全表扫描(无索引):
RPS = 549,avg = 175.8ms
结论:少加一个索引,吞吐直接除以 10,延迟乘以 10。
到了 c=500 时,无索引场景的 p99 直接破 1 秒。原因很简单:8 个 Worker 全部被 MySQL 全表扫描阻塞,请求在 PHP 队列里雪崩。线上如果漏一条这样的 SQL,不用等流量高峰,日常用户就能把服务拖死。
📈 3. 并发饱和:为什么 c=500 比 c=100 吞吐没涨?
观察 /bench/hello:c=100 时 RPS 为 7778,c=500 时为 8035,几乎没涨,但平均延迟从 12ms 飙到了 61ms(5 倍)。
这说明:8 个 Worker 的处理能力已经达到物理极限(CPU/IO 饱和)。 此时增加并发用户数,只会让请求在队列里排队,无法提升吞吐。如果你线上监控发现 RPS 上不去但延迟暴涨,说明 Worker 数量或下游依赖(DB/Redis)已经是瓶颈了。
⚠️ 4. Redis 场景的异常波动
Redis 场景数据在 3078 ~ 5821 之间大幅摇摆。排查后发现是 Redis 服务端开启了 RDB 快照或 AOF 重写,导致每隔一段时间出现 fsync 阻塞。如果你的 Redis 也有类似波动,建议将 appendfsync 改为 no 或错开重写时间。
五、容量估算(它能支撑多少 DAU?)
按互联网行业惯例:峰均比 = 4,单用户日均请求 15 次(含页面、接口、轮询)。
| 场景 | 峰值 RPS | 日均 QPS | 日总 PV | 预估 DAU |
|---|---|---|---|---|
| 纯 JSON 路由 | 8035 | ~2009 | 1.73 亿 | ~1150 万 |
| Redis 缓存业务 | 5700 | ~1425 | 1.23 亿 | ~820 万 |
| MySQL 主键查询 | 5375 | ~1344 | 1.16 亿 | ~770 万 |
| 连表复杂业务 | 4627 | ~1157 | 1.00 亿 | ~670 万 |
| CPU 密集计算 | 3152 | ~788 | 6800 万 | ~450 万 |
| 缺索引(灾难) | 556 | ~139 | 1200 万 | 仅 80 万 |
结论:优化良好的 8 核 FrankenPHP 具备支撑 千万级 DAU 的能力。但若缺一个索引,再好的架构也白搭。
六、给生产环境的硬核建议
SQL 审查是生死线
EXPLAIN必须成为 CI 流程的一环。一旦看到rows> 1000,立即阻塞合并。Worker 数量 = CPU 核心数(在 8 核下已验证最优)
不要盲目增加 Worker,超过核心数只会增加上下文切换开销,不会提升 RPS。max-requests别设 0
建议设为5000 ~ 10000,防止长驻内存导致的内存泄漏累积(尤其是 Eloquent 缓存和第三方包)。监控 p99,而不是平均响应时间
压测时 p99 的飙升往往比平均值的缓慢上升能更早暴露队列雪崩和慢查询。Redis 连接池和持久化策略需要压测验证
不要在压测期间让 Redis 做 RDB/AOF 重写,否则数据会像我的波动一样没法看。
七、总结:Laravel 进入“硬核高并发”时代
这组数据放在五年前的 PHP-FPM 时代是不可想象的。FrankenPHP + Octane 让 Laravel 真正摆脱了“每次请求重新加载框架”的桎梏。
8 核机器、真实 DB、真实 Redis,纯业务接口稳在 5000+ RPS——这个成绩已经可以正面硬刚大部分优化一般的 Go 和 Java 业务应用。
但是,工具再强,也怕 SQL 少个 WHERE 字段的索引。
性能的底线,永远掌握在工程师对数据库的敬畏心里。
欢迎大家在评论区交流你们的压测数据!
如果你也在生产环境用 FrankenPHP,分享一下你的 Worker 配置和峰值 RPS 吧。👇
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
推荐文章: