首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >FrankenPHP:PHP 的未来?

FrankenPHP:PHP 的未来?

作者头像
Tinywan
发布2026-07-01 12:27:10
发布2026-07-01 12:27:10
1460
举报
文章被收录于专栏:开源技术小栈开源技术小栈

概述

在这篇文章中,将介绍多年来 PHP 应用程序是如何被服务的——从经典的设置如 CGI、mod_php 和 PHP-FPM,到更新的选项如 Swoole、RoadRunner 和 FrankenPHP。

将深入探讨它们的工作原理、它们之间的差异以及它们带来的挑战。重点之一是共享内存模型,将分享一些实用的技巧,教你如何调整代码和思维方式,以充分利用它。

PHP 请求处理的演变

最初,每个人都使用 CGI——通用网关接口。每个对服务器的请求都会触发一个新的进程启动。这有明显缺点:在操作系统中启动进程会产生开销。每个进程都有自己独立的内存池。出于同样的原因,进程在设计上不会共享资源,例如持久的数据库连接。这正是 PHP 早期版本所依赖的。

由于上述原因,PHP 团队开始寻找更快的解决方案。mod_php 就此诞生。

mod_php 在为基于 PHP 的网站提供服务方面是一场革命。它将 PHP 直接与 Apache 服务器绑定(而在 CGI 模型中它可以与任何服务器配合使用),但带来了切实的好处。它消除了典型的 CGI 问题:无需创建新进程,解释器代码保留在内存中,数据库连接可以在请求间共享。

以下是使用 mod_php 处理请求的示意图。当服务器启动时,会创建一个工作池(根据 Apache 设置如 StartServers、MaxRequestWorkers 等参数),等待接收任务。浏览器发送请求,并路由到第一个可用的工作进程。在 Apache 中,每个工作进程可以处理任何类型的任务。当检测到 PHP 代码时,工作进程处理源代码,并将结果返回给浏览器。

图1:使用 Apache + mod_php 处理 PHP 请求

它仍然不够快。由于每个工作进程可以处理任何类型的任务——例如提供 CSS、JS 或 HTML 文件——它需要为所有功能分配相同数量的内存。在提供静态文件时,它仍然必须准备好处理 PHP 代码,反之亦然。这种方法是 PHP 的默认方式,直到 2010 年之前都没有替代方案。

在追求更好性能的探索中,PHP 世界转向了 PHP-FPM 模型——FastCGI 进程管理器。从 5.3.3 版本开始,它成为官方 PHP 发行版的一部分。

它提供了什么?一个专门用于处理 PHP 代码的工作池。这意味着更低的内存使用、更快的代码执行,以及回归到一个通用协议,允许使用不同的 Web 服务器。FPM 最常见的搭配是 Nginx,但 FastCGI 也适用于 Apache、LightHttp、IIS 等。

在这种模式下,如何为 PHP 页面提供服务?系统上会启动一个 FPM 进程,该进程会启动一个定义好的工作进程池(根据 pm = dynamic|static|ondemand 设置),然后监听一个端口或套接字以接收任务。一个运行在同一台或不同机器上的 Web 服务器等待来自浏览器的请求。它使用 FastCGI 协议将这些请求转发给 FPM。FPM 定位所需的 PHP 文件(注意文件选择由 Web 服务器处理——例如,Nginx 配置中的 try_files),处理 PHP 代码,并通过 FastCGI 连接返回结果。

图2:使用 nginx 和 fpm 处理请求

这种模式已经使用了多年,并且仍然是服务 PHP 应用程序的默认方式。

新途径:迈向现代 PHP 运行时

在 PHP-FPM 推出后的几年里,Swoole 项目应运而生。Swoole 是一个异步引擎,深受 Node.js 的启发。它提供了一种事件驱动、非阻塞 I/O 的异步编程模型。借助它,你可以轻松构建 TCP/IP、UDP、Socket、WebSocket 等类型的服务器。请求的处理方式与之前描述的模型完全不同。

理论上,Swoole 无需任何 Web 服务器即可直接处理请求,但在实践中,它几乎总是被放置在作为负载均衡器的反向代理之后。PHP 代码以 CLI 模式执行(php -f server.php),这会启动定义数量的工作进程(根据示例中 worker_num 的设置),并在指定端口上开始监听。

图3:(open)Swoole

它接收来自浏览器(或通过中间的反向代理)的请求,触发事件请求,并将响应发送回浏览器。此外,Swoole 支持并发代码执行和许多其他功能。然而,与其他本文讨论的模型相比,它需要应用程序进行最显著的变化。最流行的框架通过 Symfony Runtime 或 Laravel Octane 支持使用 Swoole 运行应用程序,但它们不提供对任何扩展附加功能的访问。

运行 PHP 应用程序的另一种方式是 RoadRunner。这个基于 Go 的应用服务器于 2018 年推出,并于 2022 年达到稳定状态。从概念上讲,它与 PHP-FPM 模型最为接近。为了处理请求,需要 RoadRunner 进程以及未修改的系统安装的 PHP 进程。RoadRunner 启动这些进程的池。在接收到请求时,它会将其编码并通过套接字发送给 PHP。

图4:在 worker 模式下使用 RoadRunner 处理 PHP 请求

除了处理 PHP 之外,RoadRunner 还提供了广泛的其他功能,使其成为最通用的应用服务器。其中包括服务器级别的中间件、gRPC、队列、键值缓存、实时消息传递、任务服务器等。它需要对应用程序进行一些修改。

最流行的框架通过多个 Symfony 运行时包支持在 RoadRunner 环境中运行应用程序。Laravel 通过官方的 Octane 包或由 RoadRunner 维护的 laravel-bridge 得到支持。RoadRunner 还推广 Spiral 框架,该框架开箱即用地利用了更多 RoadRunner 的功能。

FrankenPHP: PHP 的未来?

FrankenPHP 于 2022 年推出,并于 2023 年 10 月达到生产就绪状态。这个极简的应用服务器是用 Go 语言编写的——就像 RoadRunner 一样。然而,它的运行方式有所不同。作者在现有的 HTTP 服务器 Caddy 之上构建了它,Caddy 提供了 HTTP/1/2/3 支持、无缝 HTTPS、基于 Go 的扩展等。在此基础上,还编译了一个特定的 PHP 运行时版本。Go 层充当了 Caddy 和 PHP 运行时之间的桥梁。

FrankenPHP 本身可以在两种模式下运行。第一种是经典模式,它作为 Nginx+PHP-FPM 的直接替代品,无需对应用程序进行任何更改。第二种是所谓的 worker 模式。

图5:使用 FrankenPHP 在工作者模式下处理 PHP 请求(使用 Caddy)

这种方法更有趣,所以我将专注于它。

当 Caddy 启动时,会创建一个 PHP 工作进程池。每个工作进程首先执行指定的工作进程文件,直到它到达对\frankenphp_handle_request(callback)的调用。此时,解释器暂停执行并等待来自 Caddy 服务器的请求。来自浏览器的请求到达 Caddy 并被传递给等待的工作进程。执行 PHP 代码,并将响应返回给服务器,然后返回给浏览器。

之后,工作进程继续执行代码,直到脚本结束或遇到另一个对\frankenphp_handle_request(callback)的调用。此外,当 Caddy 执行优雅关闭时,\frankenphp_handle_request(callback)会返回 false。

在下面的屏幕截图中展示了一个最小示例。工作进程执行的代码用紫色高亮显示,而请求处理上下文中执行的代码用绿色高亮显示。

图6:执行代码

超全局变量(如_GET、_POST 以及

  • 小技巧1:如果你需要初始值,请在第一次回调执行之前将它们赋值给变量。
  • 小技巧2:如果你正在使用任何类型的请求包装器/实现/抽象,请确保只在回调函数内部创建它。

从概念上讲,FrankenPHP 最接近带有 mod_php 的 Apache:一个功能齐全的 Web 服务器(Apache 或 Caddy),内置了特定版本的 PHP 运行时。

共享内存模型存在于这里讨论的三种 PHP-FPM 最受欢迎的替代方案中。RoadRunner 和 FrankenPHP 的性能提升主要来自于使用这种模型。这一点很重要,因为与普遍认为的情况相反,在这种情况下 PHP 代码并不是并发执行的。没有线程、协程或进程。也没有异步操作或非阻塞 I/O。这使得将应用程序迁移到这些运行时环境变得更加容易。

从描述中,人们可能会得出 RoadRunner 和 FrankenPHP 相似的结论。这是真的,但它们也存在根本性的差异,不仅在于它们如何处理 PHP,还在于它们的服务器组件。让我们从这一点开始:Caddy 支持比 RoadRunner 内置服务器更多的 HTTP 请求处理相关功能。

表1:比较 FrankenPHP 和 RoadRunner

表2:FrankenPHP 与 RoadRunner 之间的差异

最大的不同在于 RoadRunner 通过调用系统安装的解释器来创建 PHP 进程池——本质上就是运行 php -f worker.php。而 FrankenPHP 则使用内置的编译版 PHP。RoadRunner 在 PHP 版本方面更灵活,但现实中我们多久会更换所使用的 PHP 版本呢?使用 FrankenPHP,我们还获得了避免进程间数据传输的优势。

然而,这两种解决方案都能显著提升性能。但要完全从中受益,我们需要调整我们的应用程序。事实上,要在“工作进程”模式下运行应用程序,我们必须准备好彻底转变我们的方法。

FrankenPHP 的一个优势是“经典”模式,它可作为 Nginx+PHP-FPM 的即插即用替代方案。根据我的测量,它在无需对应用程序进行任何更改的情况下,性能提升了约 50%(平均从 300 毫秒降至 180 毫秒)。此外,网上也有针对经典模式和工作者模式的其它基准测试,例如这里。但和所有基准测试一样,你的结果可能会有显著差异。在做出决定前先进行测量!

这种性能提升来自哪里?在 FrankenPHP 中,PHP 代码与服务器在同一个进程中运行(类似于 Apache 与 mod_php),这消除了进程间通信的开销。

图7:在常规模式下使用 FrankenPHP 处理 PHP 请求(使用 Caddy)

共享内存的新纪元

默认情况下,PHP 使用“无共享”模型。每次请求处理完毕后,所有内容(不包括 opcache、APCu 和持久数据库连接)都会从内存中清除。这使得编写代码变得非常简单。在 PHP 世界(至少在 HTTP 请求的上下文中运行时),我们无需担心内存管理或全局状态。

共享内存模型意味着两个完全不同的请求,一个接一个地发送,可以在同一个服务器上处理,而无需在中间进行任何“重置”。切换到这种共享模型将是自 PHP 5.x 引入现代对象模型以来最大的挑战。

其他语言,大多是编译型语言,一直使用共享内存模型。在 Java、.NET 或 Go 中编写和运行应用程序时,它们天生就设计为能够处理生命周期中的多个请求。

图8:框架和依赖项

你的代码应该如何准备?最重要的步骤是仔细规划你的对象生命周期——尤其是在使用具有共享实例的依赖容器时。任何依赖于请求或用户设置(组、权限、偏好等)的内容都必须始终返回为新对象。例如,当从容器中检索请求对象时,你必须确保它是当前的,而不是来自先前请求的遗留对象。

也不建议将整个容器传递到各处并存储在对象属性中——在一个请求中做出的更改可能会影响后续请求。不得使用全局变量和全局状态。应尽可能避免单例。即使将值存储在 $this 中也可能存在问题。通过设置器设置依赖关系时需要格外小心。

我们真的孤军奋战吗?并非如此。

Laravel 用户可以依赖 Octane 模块。它是一个框架的扩展,简化了在应用服务器上运行 Laravel 应用程序。它支持 FrankenPHP、RoadRunner 和 Swoole。一个有用的实现技巧:Octane 和 FrankenPHP 有时难以区分哪些请求应该由工作进程处理。

为解决这个问题,我建议在 index.php 的第一行添加 die('i should not be here'),或者重命名/删除该文件,只保留工作进程文件。Symfony 用户的情况稍好一些。2021 年初,Symfony 引入了 Runtime 组件。它基于 PSR-7 和 PSR-15,原生支持 Symfony HttpKernel,该组件也被其他框架使用。它成为 https://github.com/php-runtime/runtime 标准的基础。该组件支持在多种环境中运行应用程序,包括 AWS Lambda、Google Cloud、ReactPHP、RoadRunner、Swoole、FrankenPHP,甚至本地机器上的 PHP-FPM。

此外,Symfony 似乎更适用于处理共享内存模型中的请求。该框架提供了一些功能,例如 Symfony\Contracts\Service\ResetInterface,它允许从容器中检索到的任何服务在请求之间重置为初始状态。Symfony 方法的一个主要优势是所有请求都由同一个文件处理:index.php,因为 Runtime 与 PHP-FPM 完全兼容。

世界上最流行的博客平台——WordPress——不幸的是,在撰写这篇文章时,无法在共享内存模式下运行。目前正在进行工作,将 WordPress 封装在 PSR-15 中,你可以在以下链接关注进展:https://github.com/WordPress-PSR/swoole。即使在那之前,你仍然可以使用经典模式来提升性能。还有其他能帮助提升性能的方法吗?是的。你可以使用静态代码分析工具。

FrankenPHP 会是 PHP 的未来吗?

此时,有些人可能会问:为什么要费这么多事?这难道不还是 PHP,只是多了几步吗?答案很简单:性能。在我运行的这个应用程序(它不使用任何流行框架)后,我观察到性能提升了 10 倍。平均请求处理时间从 300 毫秒降至 30 毫秒。只需极小的代码更改。在相同的机器上。

使用 FPM 的 PHP 每秒处理 35 个请求,而以工作模式运行的 FrankenPHP 每秒处理 382 个请求。但正如我之前提到的,基准测试的问题在于你的结果可能会有显著差异。在做出改变前先进行测量!

所以,来回答这个问题:是的,我相信 FrankenPHP 是 PHP 的未来!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PHP 请求处理的演变
  • 新途径:迈向现代 PHP 运行时
  • FrankenPHP: PHP 的未来?
  • 共享内存的新纪元
  • FrankenPHP 会是 PHP 的未来吗?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档