- 后端
【免费下载链接】php-pm
PPM is a process manager, supercharger and load balancer for modern PHP applications.
本篇技术指南以 php-pm 项目的 README.md 为骨架,围绕 PHP-PM 的设计动机、Docker 快速上手、命令行与全量配置项、性能调优、调试技巧及框架适配展开,并结合仓库源码(src/ProcessManager.php、src/ProcessSlave.php、src/Commands/ConfigTrait.php 等)进行纵深解析。读完本文,你将掌握 PHP-PM 的常驻内存 worker 架构原理、
ppm start/stop/status/reload/config的完整用法、面向 Symfony/Laravel 等应用的部署与压测调优方案,以及热代码重载、静态文件服务、异常恢复等机制的底层实现。
一、PHP-PM 是什么:消除 PHP 的重复引导开销
PHP-PM(PHP Process Manager)是一个面向现代 PHP 应用的进程管理器、加速器(supercharger)与负载均衡器。它的核心思想非常直接:传统 PHP-FPM 模式下,每个请求都要重新经历 PHP 引擎的完整引导过程——声明符号、加载与解析文件、再引导功能丰富的框架(例如整个 Symfony Kernel)——这些开销是纯重复劳动。PHP-PM 的做法是让这些昂贵的引导只发生一次,然后让多个常驻内存的 worker 进程复用引导结果,从而极大提升请求吞吐。
项目基于 ReactPHP 构建,对使用请求-响应(request-response)模型的框架(如 Symfony 的 HttpKernel)效果最佳。从 composer.json 可以看到,PHP-PM 直接依赖react/event-loop、react/http、react/socket、react/child-process、react/stream以及symfony/console、symfony/process、symfony/error-handler等组件,运行环境要求 PHP^7.3 || ^8.0,且必须启用ext-pcntl扩展(源码 src/functions.php 中提供了pcntl_installed()与pcntl_enabled()两个函数在 worker 启动时做检查,要求pcntl_signal、pcntl_signal_dispatch、pcntl_waitpid未被disable_functions禁用)。
1.1 常驻内存 Worker 架构
从源码结构看,PHP-PM 采用「主进程(Master)+ 多个 Worker(Slave)」的进程模型:
- ProcessManager(主进程/负载均衡器):见 src/ProcessManager.php。负责监听 HTTP 端口、创建与调度 worker、收集 worker 上报的文件清单与内存统计、处理终止/重载信号。
- ProcessSlave(Worker 进程):见 src/ProcessSlave.php。每个 worker 是一个独立的 PHP 进程,由主进程通过
php-cgi可执行文件拉起(ProcessManager.php 中newSlaveInstance()使用['exec', $this->phpCgiExecutable, '-C', $file]构造命令行,并说明不能使用-q参数,否则会破坏 header 支持),worker 内完成应用引导(bootstrap)后常驻内存,等待处理请求。 - Slave 池与状态机:见 src/Slave.php 与 src/SlavePool.php。每个 Slave 有明确的生命周期状态:
CREATED(进程已创建未注册)→REGISTERED(已向主进程注册 PID)→READY(应用引导完成、可接收请求)→BUSY(正在处理请求)→CLOSED/LOCKED(待终止/优雅等待终止)。 - 进程间通信:主进程与 worker 通过 Unix Socket 通信,消息格式为 JSON 加换行分隔(见 src/ProcessCommunicationTrait.php),协议命令包括
register、bootstrap、ready、files、stats、log、status、stop、reload等。
用一句话概括工作流程:主进程启动 N 个 worker → 每个 worker 连接主进程注册 → 主进程下发 bootstrap 指令 → worker 引导应用后上报 ready → 主进程对外提供 HTTP 服务并把每个请求转发给空闲 worker → worker 处理完后回到 ready 状态等待下一个请求。
1.2 核心特性一览
README 明确列出的特性包括:
- 相比 PHP-FPM(针对 Symfony 应用)最高可达 15x 的性能提升(README 声称的性能数据,实际收益取决于应用与硬件环境);
- 内置负载均衡器(Integrated load balancer);
- 热代码重载(Hot-Code reload,PHP 文件变化时自动重启 worker);
- 静态文件服务,方便开发流程;
- 对 HttpKernel(Symfony/Laravel)、Drupal(实验性)、Zend(实验性)的支持。
二、快速上手:Docker 一键运行
README 推荐优先使用 Docker,因为 Docker 更易搭建与维护。官方提供了phppm/nginx镜像,把应用目录挂载进容器即可启动。
2.1 运行 Symfony 应用
cd into/your-application docker run -v "$(pwd):/var/www" -p 8080:80 phppm/nginx --bootstrap=symfony --static-directory=web/2.2 运行 Laravel 应用
cd into/your-application docker run -v "$(pwd):/var/www" -p 8080:80 phppm/nginx --bootstrap=laravel --static-directory=public/注意:上述命令把容器 80 端口映射到本机 8080,
--static-directory指向应用公开静态资源目录(Symfony 为web/,Laravel 为public/)。
如果你的应用需要额外的环境工具或库,可以在官方镜像基础上构建自己的镜像(参见 PHP-PM Docker 仓库的相关说明)。
debug 模式下的热重载:当debug开启时,PHP-PM 会检测文件变化并自动重启 worker(详见本文「热代码重载机制」一节)。
不使用 Docker 的场景:README 指出可参考官方 Wiki 中「Use without Docker」一文完成裸机安装(包括安装依赖、配置 Nginx 反向代理、直接运行ppm二进制等)。
三、命令行工具与完整配置项
PHP-PM 通过bin/ppm可执行文件(见 composer.json 的"bin"字段)暴露一组基于 Symfony Console 的命令。仓库 src/Commands 目录下共有五个命令:
| 命令 | 说明 | 对应源码 |
|---|---|---|
ppm start | 启动服务(阻塞运行) | src/Commands/StartCommand.php |
ppm stop | 停止服务 | src/Commands/StopCommand.php |
ppm status | 查看所有进程状态 | src/Commands/StatusCommand.php |
ppm reload | 重载所有 worker | src/Commands/ReloadCommand.php |
ppm config | 生成/查看ppm.json配置文件 | src/Commands/ConfigCommand.php |
stop、status、reload三个命令通过 src/ProcessClient.php 连接主进程的 Unix Socket,分别发送stop、status、reload指令,主进程收到后执行对应逻辑(src/ProcessManager.php 中的commandStop()、commandStatus()、commandReload())。
3.1ppm start的完整参数表
README 中并未给出参数表,但所有可选项均定义在 src/Commands/ConfigTrait.php 的configurePPMOptions()中,下表为完整的参数、默认值及含义(参数可通过命令行--xxx=value传入,也可写入ppm.json):
| 参数 | 默认值 | 含义 |
|---|---|---|
--bridge | HttpKernel | 将 React/PSR-7 请求转换为目标框架请求的桥接器(Bridge)类名 |
--host | 127.0.0.1 | 负载均衡器监听地址 |
--port | 8080 | 负载均衡器监听端口 |
--workers | 8 | Worker 进程数量,建议至少等于 CPU 核心数 |
--app-env | dev | 应用引导时使用的环境(如prod、dev) |
--debug | 0 | 是否开启调试(1|0),开启后应用输出更详细,并启用热代码重载 |
--logging | 1 | 是否向 stdout 输出 HTTP 访问日志(1|0) |
--static-directory | '' | 静态文件根目录;不提供则不做静态文件服务 |
--max-requests | 1000 | 每个 worker 处理的最大请求数,达到后自动重启 |
--max-execution-time | 30 | 单个请求允许执行的最大秒数,超时则关闭 worker |
--memory-limit | -1 | 单个 worker 允许消耗的最大内存(MB),-1表示不限制;达到后自动重启 |
--limit-concurrent-requests | null | 内部 ReactPHP HTTP 服务的最大并发请求数;未设置时使用 ReactPHP 默认逻辑 |
--request-body-buffer | null | 内部 ReactPHP 服务的请求体缓冲大小(字节),默认65536 |
--ttl | null | Worker 生存时间(Time to Live),到期后自动重启 |
--populate-server-var | 1 | 是否用请求数据填充$_SERVER变量(1|0),供依赖$_SERVER的老式应用使用 |
--bootstrap | PHPPM\Bootstraps\Symfony | 负责引导应用的 Bootstrap 类名 |
--cgi-path | 自动探测 | php-cgi可执行文件的完整路径 |
--socket-path | .ppm/run/ | 存放 Unix Socket 文件的目录(相对 working-directory 或 cwd) |
--pidfile | .ppm/ppm.pid | 存放主进程 PID 的文件路径 |
--reload-timeout | 30 | 重载期间等待 worker 优雅退出的秒数,超时强制关闭;-1表示禁用 |
--decorate-workers-output | 1 | 是否在服务端输出中附带额外的 worker 信息(1|0) |
--config/-c | '' | 指定配置文件路径 |
其中--cgi-path若未在命令行或配置文件中指定,会通过PhpExecutableFinder自动探测(依次尝试php-cgi与php-cgi{版本号}两种命名),探测失败会报错提示手动指定--cgi-path=(见 src/Commands/ConfigTrait.php 的loadConfig())。
3.2 配置文件ppm.json
ppm config命令会把当前生效的完整配置以 JSON 格式写入项目根目录的ppm.json(默认路径,可用-c指定其他路径),方便团队共享与版本管理。配置加载的优先级逻辑在 src/Commands/ConfigTrait.php 的optionOrConfigValue()中:命令行参数优先于配置文件,配置文件优先于内置默认值。启动时若检测到配置文件存在且被命令行参数修改,终端会提示Read configuration ... modified by command arguments。
一个典型的ppm.json示例(对应 README 性能测试中的生产配置):
{ "bridge": "HttpKernel", "host": "127.0.0.1", "port": 8080, "workers": 20, "app-env": "prod", "debug": 0, "logging": 0, "static-directory": "web", "max-requests": 1000, "max-execution-time": 30, "memory-limit": -1, "populate-server-var": 1, "bootstrap": "PHPPM\\Bootstraps\\Symfony", "socket-path": ".ppm/run/", "pidfile": ".ppm/ppm.pid", "reload-timeout": 30, "decorate-workers-output": 1 }四、性能调优建议
README 给出了明确的性能调优要点:
- 生产环境配置:使用
--app-env=prod,并关闭调试--debug=0; - 关闭 Xdebug:确保 xdebug 处于禁用状态(其开销会显著拉低吞吐);
- 调整 worker 数量:建议尝试不同数量的 worker。经验值通常是「CPU 核心数 + 10%」,例如 8 个真实核心(不含超线程)时使用
--workers=9; - 更换事件循环:对于静态文件服务或响应较快的应用,可以尝试不同的 ReactPHP 事件循环实现(如 LibEvent 等)以进一步提升性能。
从源码看,worker 数量即主进程createSlaves()循环创建的子进程数(src/ProcessManager.php),每个 worker 独立占用一个监听端口(CONTROLLER_PORT + i,控制器端口常量CONTROLLER_PORT = 5500,因此第一个 worker 端口为 5501)。
4.1 README 公布的基准数据(requests/s)
以下数据来自 README「Performance (requests/s)」一节,测试环境为 6x4GHz Intel i7、16GB RAM,压测命令为ab -c 10 -n 1000 http://127.0.0.1:8080/,仅供参考:
Symfony CMS 应用(Jarves),启动命令ppm start --bootstrap=symfony --app-env=prod --logging=0 --debug=0 --workers=20:
| PHP 版本 | 动态页面(Dynamic at Jarves) | 静态文件(Static file) |
|---|---|---|
| 7.0.3, StreamSelectLoop | 2387.67 | 3944.52 |
| 5.6.18, StreamSelectLoop | 1663.56 | 2636.09 |
| 5.6.18, LibEventLoop | 1811.76 | 3441.72 |
Laravel 示例应用,启动命令ppm start --bootstrap=laravel --app-env=prod --debug=0 --logging=0 --workers=20,README 中配图展示了压测结果(图片位于外部资源,仓库内未收录)。
五、调试技巧
5.1 单 Worker + 详细日志定位诡异问题
如果应用出现来历不明的奇怪问题,README 建议:
ppm start --workers=1 -v # 或更详细 ppm start --workers=1 -vv-v/-vv对应 Symfony Console 的 verbose 模式,会打印 worker 注册、bootstrap、ready 等每一步的状态(源码中大量isVeryVerbose()分支即为此服务)。
5.2 Xdebug 与 PHPStorm 的配合
调试时可以照常使用 Xdebug:设置断点并暂停应用时,只有正在处理该请求的那一个 worker 被停住,其余 worker 完全正常,不影响线上流量。
重要注意事项:由于 php-pm 至少包含两个进程(主进程 + worker),因此同时存在两个 Xdebug 实例。PHPStorm 默认只接受一个连接,必须提高「同时连接数」配置,否则无法用 Xdebug 调试应用。
5.3 STDOUT/STDERR 的输出语义
这是新手最容易踩坑的点,README 明确说明:
- 所有 worker 的STDOUT 被重定向到相连的客户端,所以
var_dump、echo不会显示在控制台上; - STDERR 没有被重定向,而是输出到控制台。因此最简单的调试方式是使用
error_log('hi'),可以在控制台上看到输出; - 默认情况下异常和错误只在控制台显示,并经由 Symfony/Debug 组件美化。
仓库还额外提供了console_log()全局函数(见 src/functions.php),它会把var_dump的结果写入php://stderr,方便在控制台观察任意变量的值。
5.4 热代码重载机制(Hot-Code Reload)
当debug=1时,主进程会注册一个每秒执行一次的定时器调用checkChangedFiles()(见 src/ProcessManager.php)。其实现要点:
- worker 在处理完每个请求后会把自己
get_included_files()及通过register_file()手动注册的文件清单上报给主进程(commandFiles/sendCurrentFiles,见 src/ProcessSlave.php); - 主进程保存每个文件的
filemtime与md5快照,每秒比对:先看 mtime,若 mtime 变化再比对内容 hash,只有时间与内容都变化才触发重载(避免touch()等操作造成误重载); - 文件被删除也会触发重载;文件变成目录则从跟踪列表移除;
- 检测到变化后调用
restartSlaves(),关闭全部 worker 并重新创建。
register_file()全局函数(src/functions.php)面向自定义模板引擎、缓存引擎等场景:如果你的应用有动态生成的文件,调用该函数可让 PHP-PM 在文件变化时自动重启所有 worker。
六、框架适配:Bridge 与 Bootstrap
PHP-PM 通过「Bridge(桥接器)」把 PSR-7/ReactPHP 的请求转换为目标框架的请求。Bridge 必须实现 src/Bridges/BridgeInterface.php(继承Psr\Http\Server\RequestHandlerInterface),提供bootstrap($appBootstrap, $appenv, $debug)与handle(ServerRequestInterface $request): ResponseInterface两个核心方法。
官方提供的适配层包括:
| 框架 | 说明 |
|---|---|
| HttpKernel(Symfony/Laravel) | 对应php-pm-httpkernel适配器,即默认--bridge=HttpKernel |
| Zend | php-pm-zend适配器,--bootstrap=Zf2 |
| CakePHP | 社区维护的cakephp-phppm |
仓库内置的 src/Bridges/StaticBridge.php 是一个纯静态文件服务的桥接器(bootstrap()为空,handle()恒返回 404),集成测试 integration_test.sh 正是用它配合--static-directory=web验证静态文件服务。
从 src/Bridges/BootstrapTrait.php 可以看出引导的规范化逻辑:bootstrap 类名会依次尝试原样、加前导反斜杠、PHPPM\Bootstraps\命名空间前缀三种形式解析;若 Bootstrap 实现了ApplicationEnvironmentAwareInterface(见 src/Bootstraps/ApplicationEnvironmentAwareInterface.php),还会调用其initialize($appenv, $debug)传入环境信息。
6.1 各框架的启动示例
Symfony(默认 bootstrap 即为 Symfony,可省略):
cd my-project docker run -v `pwd`:/var/www -p 8080:80 phppm/nginx --static-directory=web/Laravel:
cd my-project docker run -v `pwd`:/var/www -p 8080:80 phppm/nginx --bootstrap=laravel --static-directory=web/Zend:
cd my-project docker run -v `pwd`:/var/www -p 8080:80 phppm/nginx --bootstrap=Zf26.2 WordPress 为什么不支持
README 直言:PPM 无法与 WordPress 一起工作,因为 WordPress 缺乏请求-响应抽象(request-response abstraction)。其架构决定了它「不可能」在一个应用进程中服务多个请求,因此该框架不在支持范围内。
6.3 Symfony 可信代理配置(获取真实 IP)
由于 PHP-PM 扮演反向代理角色,应用收到的REMOTE_ADDR是 PPM 的地址(默认127.0.0.1)。要在 Symfony 应用中获取真实客户端 IP,需要在app/config/config.yml中把 PPM 加入可信代理:
# app/config/config.yml # ... framework: trusted_proxies: [127.0.0.1]源码佐证:主进程在转发请求时,会把客户端真实 IP/端口写入X-PHP-PM-Remote-IP、X-PHP-PM-Remote-Port两个请求头(见 src/RequestHandler.php 的handleData()与replaceHeader()),worker 端(src/ProcessSlave.php 的onRequest()与prepareEnvironment())读取这两个头并填充到$_SERVER['REMOTE_ADDR']/$_SERVER['REMOTE_PORT'],处理完毕后会移除这两个内部请求头。
七、负载均衡与 Worker 生命周期管理
7.1 请求分发
主进程收到外部连接后(onRequest(),src/ProcessManager.php),创建 src/RequestHandler.php 处理:从 SlavePool 中取一个READY状态的空闲 worker,将其标记为BUSY,通过 Unix Socket 把请求转发过去;若当前没有空闲 worker,则每 10ms 重试一次,超过 10 秒超时则向客户端返回 503(Service Temporarily Unavailable)。worker 处理完毕断开连接后,slaveClosed()会检查是否需要回收该 worker。
7.2 Worker 自动回收机制
为对抗内存泄漏与状态污染,PHP-PM 提供三层自动回收(对应--max-requests、--memory-limit、--ttl三个参数),触发条件在 src/RequestHandler.php 的slaveClosed()与tryOccupySlave()中实现:
- 按请求数回收:worker 处理请求数达到
max-requests(默认 1000)即关闭重建,日志输出Restart worker #N because it reached max requests of ...; - 按内存回收:worker 每个请求结束后上报
memory_get_peak_usage(src/ProcessSlave.php 的handleRequest()发送stats命令),超过memory-limit(MB)即重启; - 按存活时间回收:worker 运行超过
ttl秒即视为过期(Slave::isExpired(),src/Slave.php),下次被选中时直接关闭重建。
7.3 错误响应与容错
集成测试 integration_test.sh 完整覆盖了异常场景:
- 502 Bad Gateway:worker 提前调用
exit()返回了无效 HTTP 响应(Script did not return a valid HTTP response. Maybe it has called exit() prematurely?),或 Bridge 抛出异常强制重启 worker(日志An exception was thrown by the bridge. Forcing restart of the worker. ...); - 503 Service Temporarily Unavailable:所有 worker 都在忙、等待超时;
- 504 Gateway Timeout:请求执行超过
max-execution-time,主进程向客户端返回 504 并关闭该 worker(src/RequestHandler.php 的maxExecutionTimeExceeded())。
另外,handleRequest()(src/ProcessSlave.php)还检测headers_sent()——如果脚本自行发送了 header(在 php-cgi 模式下会破坏后续请求),会强制重启该 worker。集成测试同样验证了--memory-limit=5时 worker 在内存超限后重启、输出because it reached memory limit of 5的行为。
7.4 优雅重载与信号处理
主进程通过pcntl注册了以下信号(src/ProcessManager.php 的run()):
SIGTERM/SIGINT:优雅关闭整个服务;SIGCHLD:回收僵尸进程;SIGUSR1:重启所有 worker(restartSlaves());SIGUSR2:优雅重载所有 worker(reloadSlaves()),即ppm reload的底层实现。
优雅重载(closeSlaves($graceful = true))会尽量等正在处理请求的 worker 完成当前请求后再关闭:忙碌中的 worker 先被lock()标记为LOCKED,处理完当前请求后在RequestHandler::slaveClosed()中真正关闭;同时用reload-timeout秒的定时器兜底,超时未退出的 worker 会被强制关闭。
八、静态文件服务
当指定--static-directory时,worker 在处理动态请求之前会先尝试静态文件命中(src/ProcessSlave.php 的serveStatic()):
- 根路径
/映射到index.html; - 不提供
.php文件的静态服务(substr($filePath, -4) === '.php'直接放行给应用); - 支持
If-Modified-Since条件请求,命中则返回 304; - 响应带
Cache-Control: max-age=3600、Expires与Last-Modified头,文件内容以 ReactPHP 流式输出; - 内置一套常用 MIME 类型映射(
mimeContentType()),未知类型回退到application/octet-stream; - 对包含过多
../的路径返回 403,防止目录穿越(路径解析见 src/Utils.php 的parseQueryPath())。
九、已知问题与使用边界
README 明确列出的已知问题(Issue)是选择技术方案时必须了解的:
- 内存泄漏:PHP-PM 自身以及你的应用中都可能存在内存泄漏,这是常驻内存模型的固有挑战——但由于 worker 会自动重启(达到
max-requests/memory-limit/ttl即重建),通常不会构成严重问题; - 不支持 ExtEventLoop:不要安装
php70-event扩展;可以尝试 LibEventLoop(php56-libevent); - Drupal 与 Zend 支持非常实验性,尚未完全可用(Drupal 可尝试 php-pm-drupal 适配器);
- Laravel 的调试器尚不能完美工作,因为每次请求后仍需重置部分状态;
- 流式响应(Streamed responses)尚未实现真正的流式输出;
- 不支持 Windows:由于依赖信号处理(pcntl),只能在 Unix 类系统上运行。
十、总结
PHP-PM 的核心价值在于把「一次引导、常驻复用」的模型落地到 PHP 生态:主进程(ProcessManager)调度多个常驻内存的 worker(ProcessSlave),通过 Bridge 适配 Symfony/Laravel 等 HttpKernel 系框架,以热代码重载、自动回收与静态文件服务为开发与生产提供便利。阅读本文后,你可以从 Docker 一键运行开始,结合ppm start的完整参数表与 integration_test.sh 的覆盖场景,逐步理解其负载均衡、优雅重载与错误容错的源码实现,再依据 README 给出的调优建议(--app-env=prod、--debug=0、关闭 xdebug、合理设置 worker 数量)为你的 PHP 应用做针对性压测与部署。值得注意的是,其 Drupal/Zend 支持仍属实验性、且不支持 Windows 与流式响应,选型前应结合这些边界条件评估。
- 后端
【免费下载链接】php-pm
PPM is a process manager, supercharger and load balancer for modern PHP applications.
相关推荐
PHP-PM负载均衡器深度解析:构建高可用PHP架构终极指南
PHP PM(PHP Process Manager)是一个革命性的进程管理器、性能优化器和负载均衡器,专为现代PHP应用程序设计。它能够将你的PHP应用性能提
后端PM2 进程管理器实战指南:守护进程、集群负载均衡、日志管理与开机自启
PM2 进程管理器实战指南:守护进程、集群负载均衡、日志管理与开机自启 PM2 是面向 Node.js / Bun 应用的生产级进程管理器,内置负载均衡器,本指
运维CLI可观测性QuickRecorder:macOS屏幕录制的终极解决方案,7种录制模式让创作更简单
QuickRecorder:macOS屏幕录制的终极解决方案,7种录制模式让创作更简单 你是否曾因macOS自带的录制工具功能有限而感到困扰?想要录制软件操作教
桌面应用音视频屏幕录制
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考