8 核 FrankenPHP 下的 Laravel Octane:压测报告

一、为什么要做这次压测?

FrankenPHP 出来有一阵子了,社区里“跑分”不少,但大多是 Hello World。我一直想知道:

  1. 在真实生产模拟下(带 DB、带 Redis、带连表),它到底能扛多少?

  2. “加个索引”和“不加索引”在 Octane 常驻内存模式下,代价量化到底是多少?

  3. 8 个 Worker 是不是上限?并发从 100 涨到 500,吞吐还能涨吗?

带着这三个问题,我搭了一套完全模拟生产的 8 核环境,硬核绑核压测。数据出来了,结论非常有意思。


二、测试环境(“实验室”条件)

为了避免“跑分作弊”,我刻意模拟了真实线上部署。

  • 物理硬件:20 核物理机(但通过 taskset -c 0-7 绑定 8 核,模拟标准 8 核云主机)

  • 服务端:FrankenPHP + Laravel Octane

  • Worker 数量8(与 CPU 核心数 1:1 匹配)

  • 压测工具wrk 绑定 8-15 核(与服务端物理隔离,避免互相干扰)

  • 缓存策略:生产模式(config:cacheroute:cacheview:cache 全开)

  • 数据层

    • MySQL:users10 万行真实数据

    • Redis:真实连接,模拟缓存读写

    • 连表:student(19,261 行) JOIN school(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 = 5396avg = 17.9ms

  • 全表扫描(无索引)RPS = 549avg = 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 的能力。但若缺一个索引,再好的架构也白搭。


六、给生产环境的硬核建议

  1. SQL 审查是生死线
    EXPLAIN 必须成为 CI 流程的一环。一旦看到 rows > 1000,立即阻塞合并。

  2. Worker 数量 = CPU 核心数(在 8 核下已验证最优)
    不要盲目增加 Worker,超过核心数只会增加上下文切换开销,不会提升 RPS。

  3. max-requests 别设 0
    建议设为 5000 ~ 10000,防止长驻内存导致的内存泄漏累积(尤其是 Eloquent 缓存和第三方包)。

  4. 监控 p99,而不是平均响应时间
    压测时 p99 的飙升往往比平均值的缓慢上升能更早暴露队列雪崩和慢查询。

  5. Redis 连接池和持久化策略需要压测验证
    不要在压测期间让 Redis 做 RDB/AOF 重写,否则数据会像我的波动一样没法看。


七、总结:Laravel 进入“硬核高并发”时代

这组数据放在五年前的 PHP-FPM 时代是不可想象的。FrankenPHP + Octane 让 Laravel 真正摆脱了“每次请求重新加载框架”的桎梏。

8 核机器、真实 DB、真实 Redis,纯业务接口稳在 5000+ RPS——这个成绩已经可以正面硬刚大部分优化一般的 Go 和 Java 业务应用。

但是,工具再强,也怕 SQL 少个 WHERE 字段的索引。
性能的底线,永远掌握在工程师对数据库的敬畏心里。


欢迎大家在评论区交流你们的压测数据!
如果你也在生产环境用 FrankenPHP,分享一下你的 Worker 配置和峰值 RPS 吧。👇

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!