阿里技术文章:浅谈 Node.js 和 PHP 进程管理
所周知,PHP 占据了服务端编程语言的半壁江山,正如汪峰在音乐圈的地位一般。随着 Node.js 逐渐走上服务端编程的舞台,关于 PHP 和 Node.js 孰优孰劣的争论也不曾间断。
垄断性的市场份额足以佐证 PHP 的优秀。并且 HHVM 虚拟机、PHP 7 的革新,也给 PHP 带来了跨越式的性能突破。然而,当我们为语言层面的性能差异喋喋不休时,却往往忽略了 Web 模型在性能表现中的权重。
从 CGI 到 FastCGI
早期的 Web 服务,是基于传统的 CGI 协议实现的。每个发送到服务器的请求,都需要经过启动进程、处理请求、结束进程三个步骤,以至于访问量增大时,系统资源(如内存、CPU 等)开销也巨大,导致服务器性能下降甚至服务中断。
图 1:简单的 CGI 流程示意
在 CGI 协议下,解析器的反复加载是性能低下的主要原因。如果让解析器进程长驻内存,那么它只需启动一次,就可以一直执行着,不必每次都重新 fork 进程,这就有了后来的 FastCGI 协议。
如果 FastCGI 仅仅做到这样,那么和 Node.js 单进程单线程的模型是基本一致的:Node.js 进程启动后保持持续运行,所有的请求都由这个进程接收和处理,当某个请求引起未知错误时,才可能致使进程退出。
事实上 FastCGI 并没有那么简单,为了保证服务的稳定性,他被设计成了多进程调度的模式:
图 2:Nginx + FastCGI 执行过程
这个过程同样可以描述为三个步骤:
- 首先,初始化 FastCGI 进程管理器,并启动多个 CGI 解释器子进程;
- 接着,当请求到达 Web 服务器时,进程管理器选择并连接一个子进程,将环境变量和标准输入发送给它,处理完成后将标准输出和错误信息返还给 Web 服务器;
- 最终,子进程关闭连接,继续等待下一个请求的到来;
从 child_process 到 cluster
我们回过头来看看 Node.js 的进程管理方式。
原生 Node.js 的单进程单线程模型是一个极易被喷的槽点。这种机制也决定了 Node.js 天生只支持单核 CPU,无法有效地利用多核资源,一旦进程崩溃,还会导致整个 Web 服务的土崩瓦解。
图 3:简单的 Node.js 的请求模型
和 CGI 一样,单一进程始终面临着可靠性低、稳定性差的问题,当真正服务于生产环境时,这样的弱点相当致命。如果代码本身足够健壮,倒可以在一定程度上避免出错,但同时也对测试工作提出了更高要求。现实中我们无法避免代码 100% 不出纰漏,有些东西容易编写测试用例,有些东西却只能依靠人肉目测。
所幸 Node.js 提供了 child_process 模块,通过简单 fork 即可随意创建出子进程。如果为每个 CPU 分别指派一个子进程,多核利用就完美实现了。于此同时,由于 child_process 模块本身继承自 EventEmitter 这个基础类,事件驱动使得进程间的通信非常高效。
图 4:简单的 Node.js master-worker 模型(扒的淘杰老湿的图)
为了简化庞杂的父子进程模型实现,Node.js 紧接着又封装了 cluster 模块,不论是负载均衡、资源回收,还是进程守护,它都会像保姆一样帮你默默地搞定一切。具体技术细节可以参考淘杰老湿的 《当我们谈论 cluster 时我们在谈论什么(上)》 和 《当我们谈论 cluster 时我们在谈论什么(下)》 ,里面有所有关于 cluster 方案的推演和实现,这里不再赘述。
在 Node.js 里,要让应用跑在多核集群上,只需寥寥几行代码就万事大吉了:
var cluster = require('cluster');var os = require('os');if (cluster.isMaster) { for (var i = 0, n = os.cpus().length; i < n; i ++) { cluster.fork(); }} else { // 启动应用...}
那么反观 FastCGI 协议,它又是如何处理这种模型的呢?
PHP-FPM 的天生缺陷
PHP-FPM 是 PHP 针对 FastCGI 协议的具体实现,也是 PHP 在多种服务器端应用编程端口(SAPI:cgi、fast-cgi、cli、isapi、apache)里使用最普遍、性能最佳的一款进程管理器。它同样实现了类似 Node.js 的父子进程管理模型,确保了 Web 服务的可靠性和高性能。
PHP-FPM 这种模型是非常典型的多进程同步模型,意味着一个请求对应一个进程线程,并且 IO 是同步阻塞的。所以尽管 PHP-FPM 维护着独立的 CGI 进程池、系统也可以很轻松的管理进程的生命周期,但注定无法像 Node.js 那样,一个进程就可以承担巨大的请求压力。
受制于服务器的硬件设施,PHP-FPM 需要指定合理的 php-fpm.conf 配置:
pm.max_children # 子进程最大数pm.start_servers # 启动时的子进程数pm.min_spare_servers # 最小空闲进程数,空闲进程不够时自动补充pm.max_spare_servers # 最大空闲进程数,空闲进程超过时自动清理pm.max_requests = 1000 # 子进程请求数阈值,超过后自动回收
和 JS 不一样的是,PHP 进程本身并不存在内存泄露的问题,每个进程完成请求处理后会回收内存,但是并不会释放给操作系统,这就导致大量内存被 PHP-FPM 占用而无法释放,请求量升高时性能骤降。
所以 PHP-FPM 需要控制单个子进程请求次数的阈值。很多人会误以为 max_requests 控制了进程的并发连接数,实际上 PHP-FPM 模式下的进程是单一线程的,请求无法并发。这个参数的真正意义是提供请求计数器的功能,超过阈值数目后自动回收,缓解内存压力。
或许你已经发现了问题的关键:尽管 PHP-FPM 架构卓越,但还是卡在单一进程的性能上了。
Node.js 天生没有这个问题,而 PHP-FPM 却无法保证,它的稳定性受制于硬件设施和配置文件的契合度,以及 Web 服务器(通常是 Nginx)对 PHP-FPM 服务的负载调度能力。
ReactPHP,事件驱动,异步执行,非阻塞 IO
对 PHP 7 的狂热掩盖了 Node.js 带来的猛烈冲击。当大家还沉醉在如何选择 HHVM 还是 PHP 7 的时候,ReactPHP 也在茁壮成长,它彻彻底底抛弃了 nginx + php-fpm 的传统架构,转而模仿并接纳了 Node.js 的事件驱动和非阻塞 IO 模型,甚至连副标题,都起得一毛一样:
Event-driven, non-blocking I/O with PHP.
鉴于大家都比较了解 Node.js,对 ReactPHP 的原理就不再赘述了,我们可以认为它就是个 PHP 版的 Node.js。拿它和传统架构(Nginx + PHP-FPM,公平起见,PHP-FPM 只开一个进程)去做对比,结果是这样的:
图 5:输出“Hello World”时的 QPS 曲线
图 6:查询 SQL 时的 QPS 曲线
我们可以看到,当事件驱动、异步执行、非阻塞 IO 被移植嫁接到 PHP 上后,即便没了 PHP-FPM 支撑,QPS 曲线依然不错,在 IO 密集型的场景下,性能甚至得到了成倍成倍的提升。
事件和异步回调机制真是太赞了,它巧妙地将大规模并发、大吞吐量时的拥堵化解为一个异步事件队列,然后挨个解决阻塞(如文件读取,数据库查询等)。
针对单进程模型的吐槽,或许有些偏激。不过显而易见的事实是,单进程模型的可靠性,在 Web 服务器和进程管理器层面是有很大的优化空间的,而高并发的处理能力取决于语言特性,说白了就是事件和异步的支持。
这两点想必是让 Node.js 天生骄傲的事情,但在 PHP 里没有得到原生支持,只能通过模拟步进操作的方式来支持类似 Node.js 的事件机制,所以 ReactPHP 其实也并没有想象中那么完美。
结束语
大部分时候,当我们比较语言优劣,容易局限在语言本身,而忽视了配套的一些关键因素。
就拿 PHP 来说,这两年听到了太多关于即时编译器(JIT)、opcode 缓存、抽象语法树(AST)、HHVM 等等之类的话题。当这些优化逐步完备,语言层面的问题,早已不再是 Web 性能的短板了。如果实在不行,我们还可以把复杂任务交给 C 和 C++,以 Node.js addon 或者 PHP 扩展的形式,轻轻松松就搞定了。
都说 PHP 是“世界上最好的语言”,既然如此,也是时候学习下 Node.js 事件驱动和异步回调,考虑考虑如何对 PHP-FPM 进行大刀阔斧的革新。毕竟不管是 Node.js 还是 PHP,我们所擅长的地方,终将还是 Web,高性能的 Web。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

PHP和Python各有优势,选择依据项目需求。1.PHP适合web开发,尤其快速开发和维护网站。2.Python适用于数据科学、机器学习和人工智能,语法简洁,适合初学者。

在PHP中,应使用password_hash和password_verify函数实现安全的密码哈希处理,不应使用MD5或SHA1。1)password_hash生成包含盐值的哈希,增强安全性。2)password_verify验证密码,通过比较哈希值确保安全。3)MD5和SHA1易受攻击且缺乏盐值,不适合现代密码安全。

PHP在电子商务、内容管理系统和API开发中广泛应用。1)电子商务:用于购物车功能和支付处理。2)内容管理系统:用于动态内容生成和用户管理。3)API开发:用于RESTfulAPI开发和API安全性。通过性能优化和最佳实践,PHP应用的效率和可维护性得以提升。

PHP是一种广泛应用于服务器端的脚本语言,特别适合web开发。1.PHP可以嵌入HTML,处理HTTP请求和响应,支持多种数据库。2.PHP用于生成动态网页内容,处理表单数据,访问数据库等,具有强大的社区支持和开源资源。3.PHP是解释型语言,执行过程包括词法分析、语法分析、编译和执行。4.PHP可以与MySQL结合用于用户注册系统等高级应用。5.调试PHP时,可使用error_reporting()和var_dump()等函数。6.优化PHP代码可通过缓存机制、优化数据库查询和使用内置函数。7

PHP仍然具有活力,其在现代编程领域中依然占据重要地位。1)PHP的简单易学和强大社区支持使其在Web开发中广泛应用;2)其灵活性和稳定性使其在处理Web表单、数据库操作和文件处理等方面表现出色;3)PHP不断进化和优化,适用于初学者和经验丰富的开发者。

PHP类型提示提升代码质量和可读性。1)标量类型提示:自PHP7.0起,允许在函数参数中指定基本数据类型,如int、float等。2)返回类型提示:确保函数返回值类型的一致性。3)联合类型提示:自PHP8.0起,允许在函数参数或返回值中指定多个类型。4)可空类型提示:允许包含null值,处理可能返回空值的函数。

PHP和Python各有优劣,选择取决于项目需求和个人偏好。1.PHP适合快速开发和维护大型Web应用。2.Python在数据科学和机器学习领域占据主导地位。

PHP适合web开发,特别是在快速开发和处理动态内容方面表现出色,但不擅长数据科学和企业级应用。与Python相比,PHP在web开发中更具优势,但在数据科学领域不如Python;与Java相比,PHP在企业级应用中表现较差,但在web开发中更灵活;与JavaScript相比,PHP在后端开发中更简洁,但在前端开发中不如JavaScript。
