资讯详情

资讯详情

FrankenPHP Worker 模式实战指南:将 PHP 应用常驻内存,实现毫秒级响应

FrankenPHP Worker 模式实战指南将 PHP 应用常驻内存实现毫秒级响应【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphpFrankenPHP 的 worker 模式worker mode允许你将 PHP 应用只引导boot一次并常驻内存此后每个请求都由已在内存中的进程直接处理省去每次请求都重新引导框架、加载配置的开销从而在几毫秒内完成响应。本指南以官方 worker 文档为骨架结合仓库源码深入讲解 worker 的启动方式Docker、独立二进制、嵌入式、自定义 worker 脚本编写、Symfony / Laravel Octane 框架集成、worker 失败恢复机制、超全局变量行为以及跨请求状态持久化等核心内容帮助你完整掌握 FrankenPHP worker 模式的原理与生产实践。理解 worker 模式一次引导多次服务传统 PHP-FPM 或内置服务器的工作方式是请求到达 → 引导框架 → 处理请求 → 销毁进程状态应用启动阶段的代价Composer 自动加载、容器编译、配置解析、连接建立等在每个请求上重复发生。FrankenPHP worker 模式反转了这一模型应用只在 worker 进程启动时引导一次随后通过frankenphp_handle_request()循环反复接收并处理 HTTP 请求进程内的静态变量、类静态属性、内存缓存等全部保留。从源码看每个 worker 是一个常驻 PHP 线程池的概念仓库根目录的 worker.go 中worker结构体维护num启动线程数、maxThreads运行时最大线程数与threads线程列表并记录maxConsecutiveFailures等失败恢复参数。worker 脚本路径在启动时经filepath.EvalSymlinks与fastabs.FastAbs解析为绝对路径并校验存在性worker.go同时通过WithRequestDocumentRoot将 worker 脚本所在目录设置为请求文档根目录。启动 worker 脚本方式一Docker 容器Docker 官方镜像默认使用内置 Caddyfile见 caddy/frankenphp/Caddyfile其中的frankenphp全局配置块通过{$FRANKENPHP_CONFIG}占位符读取FRANKENPHP_CONFIG环境变量。因此只需把该环境变量的值设为worker /path/to/your/worker/script.phpdocker run \ -e FRANKENPHP_CONFIGworker /app/path/to/your/worker/script.php \ -v $PWD:/app \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphp方式二独立二进制使用php-server命令的--worker选项即可用 worker 方式伺服当前目录内容frankenphp php-server --worker /path/to/your/worker/script.phpphp-server的完整用法见 caddy/php-server.go支持--domain、--root、--listen、--watch、--access-log、--debug、--mercure、--no-compress等选项其中--worker可重复指定多个 worker并支持--worker/path/to/worker.php,nb-workers语法直接指定线程数caddy/php-server.go。如果你的 PHP 应用已经嵌入二进制文件则可在应用根目录放一个自定义Caddyfile它会被自动使用php-server在检测到嵌入应用时会chdir到应用目录并优先加载其中的Caddyfilecaddy/php-server.go。文件变更时自动重启 workerworker 模式常驻内存PHP 文件改动不会自动生效。可以用--watch选项监听文件变更并触发重启。下面的命令会在/path/to/your/app/目录及其子目录中任何以.php结尾的文件被修改时重启 workerfrankenphp php-server --worker /path/to/your/worker/script.php --watch/path/to/your/app/**/*.php该功能通常与热重载hot reloading搭配使用。若省略--watch的 glob 参数则使用默认模式./**/*.{env,php,twig,yaml,yml}对应 caddy/php-server.go 中的defaultWatchPattern。在 Caddyfile 中也可用watch指令实现同样的效果详见 docs/config.md{ frankenphp { worker { file /path/to/app/public/worker.php watch /path/to/app/**/*.php # 监听 /path/to/app 及其子目录下所有 PHP 文件 } } }注意**表示递归监听若定义了多个 worker任意文件变化会重启全部 worker应避免监听运行时产生的文件如日志以免触发意外重启。框架集成Symfony 与 Laravel OctaneSymfony 运行时[!TIP] 本节内容仅适用于 Symfony 7.4 之前的版本从 Symfony 7.4 起 worker 模式已获原生支持。FrankenPHP 的 worker 模式由 Symfony Runtime Component 支持。要在 worker 中启动任意 Symfony 应用先通过 PHP Runtime 的 FrankenPHP 包安装composer require runtime/frankenphp-symfony然后通过APP_RUNTIME环境变量指定 FrankenPHP Symfony Runtime 并启动应用服务器docker run \ -e FRANKENPHP_CONFIGworker ./public/index.php \ -e APP_RUNTIMERuntime\\FrankenPhpSymfony\\Runtime \ -v $PWD:/app \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphpLaravel OctaneLaravel 应用请直接参考专属文档中关于 Laravel Octane 与 FrankenPHP worker 模式集成的详细说明FrankenPHP 是 Octane 官方支持的服务器之一。编写自定义 worker 脚本不依赖任何第三方库自己编写 worker 脚本完全可行。核心思路是先引导应用再定义请求处理回调然后在一个循环中反复调用frankenphp_handle_request()?php // public/index.php // 引导你的应用 require __DIR__./vendor/autoload.php; $myApp new \App\Kernel(); $myApp-boot(); // 处理器放在循环外以获得更好性能循环内少做事 $handler static function () use ($myApp) { try { // 收到请求时被调用 // 超全局变量、php://input 等此时已被重置 echo $myApp-handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); } catch (\Throwable $exception) { // set_exception_handler 只在 worker 脚本结束时才被调用 // 这可能不符合预期因此要在这里捕获并处理异常 (new \MyCustomExceptionHandler)-handleException($exception); } }; $maxRequests (int)($_SERVER[MAX_REQUESTS] ?? 0); for ($nbRequests 0; !$maxRequests || $nbRequests $maxRequests; $nbRequests) { $keepRunning \frankenphp_handle_request($handler); // 发送 HTTP 响应之后做点收尾工作 $myApp-terminate(); // 主动调用垃圾回收器降低其在页面生成中途被触发的概率 gc_collect_cycles(); if (!$keepRunning) break; } // 清理 $myApp-shutdown();随后启动应用并用FRANKENPHP_CONFIG配置 workerdocker run \ -e FRANKENPHP_CONFIGworker ./public/index.php \ -v $PWD:/app \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphpworker 数量配置默认情况下每个 CPU 启动 2 个 worker。也可以显式配置启动数量worker指令的第三个参数即 worker 数量docker run \ -e FRANKENPHP_CONFIGworker ./public/index.php 42 \ -v $PWD:/app \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphp对应地Caddyfile 全局配置解析逻辑见 caddy/app.go支持num_threads默认 2 倍 CPU 数、max_threads运行时最多线程数默认 2 倍num_threads以及worker子指令块caddy/workerconfig.go中的num、max_threads、env、watch、match、max_consecutive_failures、name、file等配置项。值得注意的还有两点从源码看当所有线程繁忙时新请求会进入worker.queuedRequests队列并触发自动扩容scaling但受max_threads上限约束若等待超过max_wait_time请求会被拒绝并返回ErrMaxWaitTimeExceededworker.go。worker指令的 worker 数量参数在解析时同样以strconv.ParseUint校验为非负整数caddy/workerconfig.go。使用 PSR-15 接口如果你的应用使用 PSR-15 而非超全局变量可以在 handler 边缘用nyholm/psr7与nyholm/psr7-server完成请求/响应转换composer require nyholm/psr7 nyholm/psr7-server psr/http-server-handler?php // public/index.php require __DIR__./vendor/autoload.php; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7Server\ServerRequestCreator; $myApp new \App\Kernel(); // 实现 Psr\Http\Server\RequestHandlerInterface $myApp-boot(); $psr17Factory new Psr17Factory(); $creator new ServerRequestCreator( $psr17Factory, // ServerRequestFactory $psr17Factory, // UriFactory $psr17Factory, // UploadedFileFactory $psr17Factory, // StreamFactory ); $handler static function () use ($myApp, $creator) { $response $myApp-handle($creator-fromGlobals()); http_response_code($response-getStatusCode()); foreach ($response-getHeaders() as $name $values) { foreach ($values as $value) { header($name: $value, false); } } echo $response-getBody(); }; $maxRequests (int)($_SERVER[MAX_REQUESTS] ?? 0); for ($nbRequests 0; !$maxRequests || $nbRequests $maxRequests; $nbRequests) { $keepRunning \frankenphp_handle_request($handler); gc_collect_cycles(); if (!$keepRunning) break; }worker 生命周期管理处理固定请求数后重启 workerPHP 最初并非为长生命周期进程设计许多库和遗留代码仍存在内存泄漏。worker 模式下使用这类代码的临时解决办法是在 worker 处理完一定数量的请求后重启它。上文自定义 worker 示例中通过MAX_REQUESTS环境变量控制最大处理请求数——这正是$maxRequests (int)($_SERVER[MAX_REQUESTS] ?? 0);的作用。将该变量置为非零值循环会在处理完指定请求数后自然退出worker 随之重启。手动重启全部 worker除了在文件变更时重启也可以通过 Caddy admin API 中启用了 admin 端点即可向重启端点发送一个简单的 POST 请求curl -X POST http://localhost:2019/frankenphp/workers/restart该端点的注册位于 caddy/admin.go其处理逻辑调用frankenphp.RestartWorkers()caddy/admin.go后者通过mainThread.rebootAllThreads()触发全部 worker 线程重启worker.go。从源码注释可见所有 worker 必须同时重启以避免 opcache 重置带来的问题。worker 失败处理与指数退避当 worker 脚本以非零退出码崩溃时FrankenPHP 会采用指数退避exponential backoff策略重启它。具体规则是若 worker 脚本存活时间超过上次退避时长 × 2则不计入惩罚并再次重启若 worker 脚本在短时间内持续以非零退出码失败例如脚本中存在拼写错误FrankenPHP 最终会以too many consecutive failures错误崩溃。连续失败次数上限可在 Caddyfile 中用max_consecutive_failures选项配置frankenphp { worker { # ... max_consecutive_failures 10 } }从源码看该选项的默认值是 6且允许设为-1表示永不崩溃见 options.go 中defaultMaxConsecutiveFailures 6及 caddy/workerconfig.go 中的解析校验取值必须大于等于 -1。配置项的有效取值包括name、file、num、env、watch、match、max_consecutive_failures、max_threads。超全局变量的行为PHP 超全局变量$_SERVER、$_ENV、$_GET等在 worker 模式下遵循以下规则在第一次调用frankenphp_handle_request()之前超全局变量保存的是 worker 脚本自身的值在调用frankenphp_handle_request()期间及之后超全局变量保存的是当前处理的 HTTP 请求产生的值且每次调用都会改写超全局变量的内容。要在回调内部访问 worker 脚本自身的超全局变量必须先把它们复制一份再将副本导入回调作用域?php // 在第一次调用 frankenphp_handle_request() 之前复制 worker 的 $_SERVER $workerServer $_SERVER; $handler static function () use ($workerServer) { var_dump($_SERVER); // 与 HTTP 请求绑定的 $_SERVER var_dump($workerServer); // worker 脚本自身的 $_SERVER }; // ...这一行为在 C 层实现中得到了印证frankenphp_reset_super_globals()见 frankenphp.c在每个请求之间重置所有自动全局变量但明确跳过$_ENV同时$_SERVER总是被重新导入always reimport $_SERVER$_FILES需要显式清空$_SESSION则会从符号表中显式删除以防跨请求泄漏。$_ENV的注意事项大多数超全局变量$_GET、$_POST、$_COOKIE、$_FILES、$_SERVER、$_REQUEST会在请求之间自动重置。但$_ENV目前不会在请求之间重置——这意味着在某个请求中对$_ENV的修改会持续存在并被同一 worker 线程处理的后续请求看到。因此应避免在$_ENV中存放与请求相关或敏感的数据。跨请求的状态持久化由于 worker 模式让 PHP 进程在请求之间保持存活以下状态会跨请求保留静态变量函数或方法中用static声明的变量会在请求之间保留其值类静态属性类的静态属性跨请求持久全局变量worker 脚本全局作用域中的变量跨请求持久内存缓存请求处理器之外保存在内存中的任何数据数组、对象都会保留。这是设计使然也正是 worker 模式高效的原因。但它要求你注意避免意外副作用?php function getCounter(): int { static $count 0; return $count; // 跨请求递增 } $handler static function () { echo getCounter(); // 该线程上每个请求依次输出 1, 2, 3, ... }; while (\frankenphp_handle_request($handler)) { // ... }编写 worker 脚本时务必在请求之间重置任何与请求相关的状态。像 Symfony 和 Laravel Octane 这样的框架会帮你重置大部分状态但你可能仍需自行重置自己的服务。以 Symfony 为例持有请求相关状态的服务应实现Symfony\Contracts\Service\ResetInterface以便内核在请求之间对其执行重置。关键要点总结worker 模式的核心是一次引导、常驻内存、反复服务显著降低每次请求的引导开销启动方式包括FRANKENPHP_CONFIG环境变量Docker、php-server --worker独立二进制以及嵌入式应用根目录的Caddyfile。自定义 worker 脚本只需围绕frankenphp_handle_request()编写引导、处理、收尾三段逻辑并可通过MAX_REQUESTS环境变量控制生命周期PSR-15 应用可通过nyholm/psr7系列库在边缘做协议转换。worker 失败会按指数退避策略重启max_consecutive_failures默认 6-1 表示永不崩溃控制连续失败上限curl -X POST http://localhost:2019/frankenphp/workers/restart可手动优雅重启全部 worker。请求之间大部分超全局变量会自动重置唯独$_ENV不会切勿在其中存放请求相关或敏感数据。静态变量、类静态属性、全局变量与内存缓存天然跨请求持久——这是性能来源也是需要主动管理状态、实现ResetInterface等重置机制的原因。【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →