CatchAdmin PHP 后台管理框架 Logo CatchAdmin

Laravel 使用 PHP TrueAsync 协程 I/O 性能实测 性能爆表啦

把 Laravel 请求放进协程里处理,性能到底能提升多少?

30%50%200%,还是 400%

Laravel Octane 已经让 Laravel 适配了有状态运行模式,使 Laravel 进程可以常驻内存运行,从而跳过每次请求重复的框架启动流程。

但是,几乎没人真正尝试过让 Laravel 变成一个“异步框架”。

过去,社区里一种常见判断是:

让 Laravel 异步化实在太难了。

然而,TrueAsync 0.6.0 发布后,爱好者 YanGusik 在没有大幅改动代码的情况下,成功适配了 Laravel 的核心部分。

这真的可能吗?

听起来像某种技巧。毕竟,很多人曾尝试让 Laravel 适配 Swoole 协程,最后诞生的是一个从零构建的新框架 —— Hypervel。

Hypervel 采用等价 API,重新实现了一套框架代码。

那么,这一次又是怎么做到的?

一、性能基准测试

测试环境

本次测试环境如下:

  • OS:WSL2,Linux 5.15
  • CPU:16 核
  • 内存:7.8 GB RAM
  • 数据库:PostgreSQL 16,max_connections=500
  • 压测工具:k6
  • 压测模式constant-arrival-rate
  • 目标请求量:1000 req/s
  • 持续时间:30 秒

说明:这些测试使用的是未开启协程模式的 Swoole,因为 Laravel 尚未适配 Swoole 协程模式。 在纯合成协程测试中,Swoole 的表现略优于 FrankenPHP + TrueAsync。两者性能接近,在 12 个 worker 下都能达到约 10,000 req/s。

测试项目地址:

https://github.com/YanGusik/ta_benchmark

二、测试工作负载

本次测试并不是简单的 “Hello World” 合成测试,而是更贴近真实 Web 应用场景的数据库 I/O 工作负载。

/bench 端点会对 PostgreSQL 顺序执行 10 条 SQL 查询,包括:

  • 用户查询
  • 文章列表查询
  • 插入浏览记录
  • 更新浏览计数
  • 聚合查询
  • TOP-N 查询

数据库中包含:

  • 100 个用户
  • 1,000 篇文章
  • 持续增长的 post_views

也就是说,这个测试重点考察的不是 PHP 空跑性能,而是典型 Web 应用中更常见的 数据库 I/O 密集型场景

三、吞吐量测试:目标 1,000 req/s

16 个 worker 下:

  • TrueAsync:987 req/s
  • Laravel Octane + Swoole ZTS:601 req/s

TrueAsync 几乎达到了 1,000 req/s 的目标。

而 Laravel Octane 的最佳结果为 601 req/s,在相同 worker 数量下,吞吐量比 TrueAsync 低约 64%

这里使用 16 个 worker,是为了给阻塞式服务器提供更有利的测试条件。

更关键的是:

FrankenPHP + TrueAsync 只需要 4 个 worker,就能像 16 个 worker 一样稳定处理每秒 1,000 个请求。

原因在于,协程会在每次 PDO::query() 时让出控制权。

当一个协程正在等待数据库响应时,当前 worker 并不会空闲等待,而是可以继续执行其他协程中的请求。

也就是说:

单个 worker 可以同时处理数十个请求。

阻塞式服务器则不同。它要达到接近 990 req/s 的吞吐量,需要更多 worker 来堆并发。

四、中位延迟 P50:差距达到 56 倍

16 个 worker 下:

  • TrueAsync:29 ms
  • Swoole Laravel Octane:1,640 ms

两者的 P50 延迟差距达到 56 倍

这些秒级延迟几乎都来自队列等待时间。

需要注意的是,PHP 代码和 SQL 的实际执行速度并没有本质差异。

真正的差异来自服务器模型:

阻塞式服务器必须等待当前请求完成后,才能开始处理下一个请求。 协程服务器则可以在当前请求等待 I/O 时,继续处理其他请求。

换句话说,阻塞式模型中的进程或线程,大量时间都花在“等待”上。

TrueAsync 的 CPU 利用率更高,是因为协程之间不会互相阻塞。它们在等待 I/O 时主动让出控制权。

这里没有魔法,只有更高效的资源利用方式。

五、负载下的内存使用

从内存使用上看,TrueAsync 同样具备优势。

在阻塞式模型中,每个 worker 通常都是一个独立进程或线程,并且都拥有一份完整的 Laravel 运行环境,包括:

  • 服务容器
  • 配置
  • 路由
  • 中间件
  • 数据库管理器

而在 TrueAsync 中,通常只需要一个或少量 worker。

多个协程共享同一套已经启动完成的框架环境。真正需要为每个请求单独复制的,主要是请求级别的数据,例如:

  • request
  • session
  • auth

因此,内存差异就体现出来了:

  • TrueAsync:308 MB
  • FrankenPHP Octane:400 MB

更低的内存使用并不是核心收益,但它是协程模型带来的额外优势。

六、用理论验证测试结果

基准测试结果可以用并发效率理论进行验证。

根据测试测量:

text
T_cpu ≈ 5 ms
T_io  ≈ 23 ms

其中:

  • T_cpu 表示 PHP 执行时间
  • T_io 表示 SQL 等待时间

阻塞系数为:

text
T_io / T_cpu = 23 / 5 = 4.6

Goetz 公式来自 Brian Goetz 的《Java Concurrency in Practice》,用于估算最优并发任务数:

text
N_opt = N_cores × (1 + T_io / T_cpu)

对于拥有 16 个 worker 的阻塞式服务器来说,每个 worker 同一时间只能处理 1 个请求,因此理论吞吐量为:

text
Throughput = 16 / (T_cpu + T_io)
           = 16 / 0.028
           ≈ 571 req/s

Octane 的实测结果为:

text
601 req/s

这个结果已经非常接近阻塞式模型的理论上限。

再看 TrueAsync。

对于使用 4 个 worker,并且最多约 200 个并发协程的 TrueAsync 来说:

text
N_opt = 16 × (1 + 4.6)
      = 89.6 coroutines

200 个协程已经足以覆盖最优并发规模。

根据 Little 定律:

text
λ = L / W

可得:

text
λ = 200 / 0.028
  ≈ 7,142 req/s

这是理论最大值。

实际测试结果约为 990 req/s,说明瓶颈并不在 CPU,而是在 PostgreSQL。

因此,理论验证了测试结论:

在 I/O 密集型工作负载中,当阻塞系数达到 4.6 时,协程模型能够比阻塞模型更高效地利用 CPU。

七、基准测试结论

1. Swoole ZTS ≈ Swoole NTS

Swoole ZTS 采用线程模型,Swoole NTS 采用进程模型。

但在本次测试中,二者吞吐量几乎没有差异。

原因很简单:

模型依旧是阻塞式的。 任意时刻,每个 worker 只能处理一个请求。

由于线程本地存储的存在,ZTS 的内存消耗反而高出约 5% - 10%

2. Octane FrankenPHP ≈ Octane Swoole

Octane FrankenPHP 和 Octane Swoole 都属于阻塞式服务器,因此会遇到相同的根本限制。

FrankenPHP 的优势主要体现在内存上:

它使用一个 Go 进程,而不是多个独立 PHP 进程,因此更节省内存。

但是在吞吐量方面,两者表现基本相同。

3. TrueAsync 的最低收益约为 30% - 40%

即使将阻塞式 worker 池扩展到 22 - 27 个,以匹配 TrueAsync 的吞吐量,TrueAsync 依然在延迟和内存消耗上占优。

问题在于:

既然 TrueAsync 用 4 个 worker 就能完成同样的工作,为什么还要消耗 22 个 worker?

这正是协程模型的核心价值。

八、核心结论

对于 I/O 密集型工作负载,TrueAsync 的优势非常明显。

而典型 Web 应用,恰恰就是 I/O 密集型场景。

本次测试可以总结为:

TrueAsync 可以用少 5 - 6 倍的 worker 承载相同请求量,将延迟从数千毫秒降低到几十毫秒,并将内存消耗接近减半。

更具体地说:

  • 阻塞式服务器需要更多 worker 才能堆出吞吐量;
  • TrueAsync 可以通过协程在 I/O 等待期间切换执行权;
  • CPU 不再大量浪费在等待数据库响应上;
  • 单个 worker 可以同时承载更多请求;
  • 延迟显著下降;
  • 内存占用也随之降低。

最终结论是:

TrueAsync 并不是让 PHP 或 SQL 本身变快了,而是让 Laravel 在 I/O 等待期间不再空转。它提升的是资源利用率,而这正是 Web 应用性能优化中最关键的一环。

本作品采用《CC 协议》,转载必须注明作者和本文链接