一个 PHP 进程能处理多个请求吗?

您可以编写持续时间超过几分钟的 PHP 脚本(最多几小时或几天):例如 Cron 任务、CSV 解析器、队列处理程序。所有这些工作遵循一个模式:他们获取一条任务,处理完它,然后获取下一个任务。代码常驻在内存中,因此避免了额外的操作来加载框架和应用程序,节约了宝贵时间。

但是开发长时间运行的脚本并不是那么容易。任何错误都会杀死进程,内存溢出会导致崩溃,而且不能用 F5 来调试程序了。

自 PHP 7 后情况有所改善:可靠的垃圾收集器出现了,它变得更容易处理错误,内核的扩展可以避免内存泄漏。是的,工程师仍然需要仔细处理内存并记住代码中的状态的问题(有哪一种语言能让你可以不关注这些事情呢?)当然,在 PHP 7 中,惊喜并不多。

是否可以采用一种 常驻 PHP 脚本的模型,将其用于处理 HTTP 请求等更琐碎的任务,从而消除对每个请求都从头开始下载所有内容的需要?

要解决这个问题,首先需要实现一个服务器应用程序,该应用程序可以接收 HTTP 请求并将它们逐个重定向到 PHP worker,而不是每次都杀死它。

我们知道我们可以用纯 PHP(PHP-PM)或 C 扩展(Swoole)编写 web 服务器。尽管每种方法都有其优点,但这两种选择都不适合我们 —— 我想要更多的东西。

我们需要的不仅仅是一个 web 服务器 —— 我们希望得到一个解决方案,可以使我们避免与 PHP 中的「重启动」相关的问题,同时可以轻松地为特定的应用程序进行调整和扩展。也就是说,我们需要一个应用服务器。

Go 可以帮助解决这个问题吗?我们知道它可以,因为这种语言将应用程序编译成单个的二进制文件;它是跨平台的;使用自己的并行处理模型(并发)和用于处理 HTTP 的库;最后,我们可以把更多的开源库集成到我们的程序中。

合并两种编程语言遇到的困难

首先,有必要确定两个或多个应用程序之间如何相互通信。

例如,使用 Alex Palaestras 的 go-php 库,可以实现 PHP 和 Go 进程 (如 Apache 中的 mod_php) 之间的内存共享。但是这个库的功能限制了我们使用它解决问题。

我们决定使用另一种更常见的方法:通过使用 sockets /pipelines 来构建进程之间的交互。这种方法在过去十年中已经证明了其可靠性,并且在操作系统级别得到了很好的优化。

首先,我们创建了一个简单的二进制协议,用于在进程之间交换数据和处理传输错误。在其最简单的形式中, 这种类型的协议类似于 一个具有固定大小的 packet 头 (在我们的示例中为 17 个字节) 的 netstring ,其中包含的信息有 packet 的类型,其大小和二进制掩码的信息,用来检查数据的完整性。

在 PHP 端,我们使用了 pack 函数 ,在 Go 端,使用了 编码 / 二进制 库。

有一个协议对我们来说有点过时,我们添加了直接 从 PHP 调用 net /rpc Go 服务 的功能。这个功能在后面的开发中对我们有很大的帮助,因为我们可以轻松地将 Go 库集成到 PHP 应用程序中。这项工作的结果可以在我们的另一个开源产品 Goridge 中看到。

在多个 PHP Worker 之间分配任务

在交互机制实现之后,我们开始思考如何更好地将任务转移到 PHP 进程中。当任务到达时,应用服务器必须选择一个空闲的 worker 来执行它。如果 worker 进程因错误而终止或「死亡」,我们将清除它并创建一个新的。如果 worker 进程成功执行,我们会将它返回到可用于执行任务的 worker 池中。

为了存储活跃的 worker 进程池,我们使用了一个 缓冲通道 , 为了从池中清除意外「死亡」的工作进程,我们添加了一种跟踪错误和 worker 进程状态的机制。

最终,我们得到了一个可以运行的 PHP 服务器,它能够处理任何以二进制形式呈现的请求。

为了让我们的应用程序作为 web 服务器开始工作,我们必须选择一个可靠的 PHP 标准来处理任何传入的 HTTP 请求。在我们的例子中,我们只需将简单的 net /http 请求从 Go 转换 为 PSR-7 格式,这样它就可以与目前大多数可用的 PHP 框架兼容。

由于 PSR-7 被认为是不可变的(有人会说在技术上不是),开发人员必须编写那些在原则上不将请求视为全局实体的应用程序。这完全符合 PHP 常驻进程的概念。我们的最终实现(尚未收到名称)如下所示:

RoadRunner - 高 - 性能 PHP 应用服务器

我们的第一个测试任务是一个 API 后端,在该后端上,会周期性地出现不可预测的突发请求(比平时更频繁)。虽然在大多数情况下 nginx capabilities 是足够的,但是我们经常因为无法在预期的负载增加下快速平衡系统而遇到 502 错误。

为解决此问题,我们在 2018 年初部署了第一台 PHP / Go 应用服务器。并立即取得了惊人的效果!我们不仅完全消除了 502 错误,并且还将服务器的数量减少了三分之二,节省了大量资金并解决了令工程师和产品经理头痛的问题。

在年中的时候,我们改进了我们的方案,在 MIT 许可下将其发布在 GitHub 上,并命名为 RoadRunner, 从而强调了它惊人的速度和效率。

RoadRunner 是如何改进你的开发堆栈的

RoadRunner 的使用允许我们在 Go 端使用中间件 net/http ,甚至在请求进入 PHP 之前进行 JWT 验证,以及在 Prometheus 中处理 WebSocket 和全局聚合状态。

由于内置的 RPC,你可以在不编写扩展包的情况下,在 PHP 中打开任何 Go 库的 API。更重要的是,使用 RoadRunner,你可以部署不同于 HTTP 的新服务器。示例包括在 PHP 中运行 AWS Lambda 处理器,创建强大的队列 选择器, 甚至将 gRPC 添加到我们的应用程序中。

同时使用 PHP 和 Go ,对解决方案有了稳定的提升,在一些测试中将应用程序性能提高了 40 倍,改进了调试工具,实现了与 Symfony 框架的集成,并添加了对 HTTPS、HTTP/2、插件和 PSR-17 的支持。

结论

有些人仍然被过时的 PHP 概念所束缚,认为 PHP 是一种缓慢、繁琐的语言,只适合在 WordPress 下编写插件。这些人甚至还说 PHP 有这样一个限制:当应用程序变得足够大时,你必须选择一种更「成熟」的语言,并重写多年积累的代码库。

对于这些问题,我的回答是:再想一想。我们相信只是你自己为 PHP 设置了一些限制。你可以用一生的时间从一种语言迁移到另一种语言,试图找到与你的需求完美结合的语言,或者你可以将语言视为工具。

像 PHP 这样的语言,它的假想缺陷可能是其成功的真正原因。如果你将它与另一种语言(如 Go)相结合,那么你将创造出比只使用一种语言更强大的产品。

在交替使用过 Go 和 PHP 之后,我们可以说我们很喜欢它们。我们不打算牺牲其中一个来换取另一个,相反,我们会想办法从这个双重架构中获得更多收益。

原文:https://sudonull.com/post/6470-RoadRunner-PHP-is-not-created-to-die-or-Golang-to-the-rescue

译文:https://learnku.com/php/t/61733

原文:https://sudonull.com/post/6470-RoadRunner-PHP-is-not-created-to-die-or-Golang-to-the-rescue

译文:https://learnku.com/php/t/61733

- EOF -

点击标题可跳转

1、 10.6K Star!酷炫的开源数据库管理工具!

2、 Type 遭库开发者嫌弃:类型简直是万恶之源

3、 一个 Python Bug 干倒了估值 1.6 亿美元的公司

↓推荐关注↓

「PHP开发者」专注分享 PHP 开发相关的技术文章和工具资源。