尧图精选

FrankenPHP Worker 模式完全指南:一次启动、常驻内存的 PHP 应用服务器

🕒 发布时间:2026/9/15 17:41:58 📁 来源:尧图网络
FrankenPHP Worker 模式完全指南一次启动、常驻内存的 PHP 应用服务器【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp导读本文以 FrankenPHP 官方文档docs/it/worker.md为主体系统讲解 Worker 模式的启动方式、自定义 Worker 编写、崩溃恢复、超全局变量行为与状态持久化等核心机制并辅以仓库源码worker.go、threadworker.go、caddy/workerconfig.go、caddy/admin.go作为实现依据。读完本文你将掌握如何把 PHP 应用一次性加载进内存、在毫秒级响应请求并能在生产环境中正确配置、监控与重启 Worker。一、Worker 模式是什么传统 PHP 的每个请求都会重新加载应用、重新解析代码。Worker 模式则相反应用只启动一次随后常驻内存由 FrankenPHP 负责把到达的 HTTP 请求快速分发到已就绪的 Worker 进程/线程上处理从而把每请求的开销降到最低。从源码看worker.go中每个worker结构体代表一个 Worker 脚本可以挂载多个线程// worker.go type worker struct { mercureContext name string fileName string num int maxThreads int ... maxConsecutiveFailures int ... }Worker 脚本通过 C 扩展暴露的frankenphp_handle_request()函数接收请求并返回响应每次调用会阻塞直到下一个请求到来。二、启动 Worker2.1 使用 Docker 启动设置环境变量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/frankenphpFRANKENPHP_CONFIG中的路径是容器内路径因此示例中与-v $PWD:/app的挂载点保持一致。2.2 使用独立二进制启动使用php-server命令的--worker选项即可用 Worker 模式伺服当前目录内容frankenphp php-server --worker /path/to/your/worker/script.php如果 PHP 应用已嵌入到二进制文件中可以在应用根目录添加自定义Caddyfile它会被自动采用无需额外传参。2.3 监听文件变化自动重启--watchWorker 常驻内存后代码修改不会自动生效。可以用--watch选项在文件变化时自动重启 Worker详见 config.md 的文件变化控制。下面的命令会在/path/to/your/app/及其子目录中任何.php文件被修改时触发重启frankenphp php-server --worker /path/to/your/worker/script.php --watch/path/to/your/app/**/*.php--watch经常与热重载Hot Reload搭配使用前者负责重启 Worker后者负责刷新浏览器页面实现接近保存即生效的开发体验。三、框架集成3.1 SymfonySymfony 用户请直接参考 Symfony Worker 模式文档其中包含与 FrankenPHP 结合的具体配置与命令。3.2 Laravel OctaneLaravel 用户可参考 Laravel Octane 文档FrankenPHP 是 Octane 官方支持的服务器之一。四、编写自定义 Worker不依赖任何第三方库用纯 PHP 就能写出自己的 Worker。下面是官方文档提供的完整示例?php // public/index.php // Avvio dellapplicazione require __DIR__./vendor/autoload.php; $myApp new \App\Kernel(); $myApp-boot(); // Gestore fuori dal ciclo per migliori prestazioni (meno lavoro per richiesta) $handler static function () use ($myApp) { try { // Chiamato quando arriva una richiesta, // i superglobali, php://input e simili vengono reimpostati echo $myApp-handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); } catch (\Throwable $exception) { // set_exception_handler viene chiamato solo quando termina lo script worker, // il che potrebbe non essere il comportamento atteso, quindi intercettare e gestire qui le eccezioni (new \MyCustomExceptionHandler)-handleException($exception); } }; $maxRequests (int)($_SERVER[MAX_REQUESTS] ?? 0); for ($nbRequests 0; !$maxRequests || $nbRequests $maxRequests; $nbRequests) { $keepRunning \frankenphp_handle_request($handler); // Esegui qualcosa dopo aver inviato la risposta HTTP $myApp-terminate(); // Richiama il garbage collector per ridurre la probabilità che parta durante la generazione di una pagina gc_collect_cycles(); if (!$keepRunning) break; } // Pulizia $myApp-shutdown();代码要点启动逻辑放循环外require autoload、boot()等昂贵操作只执行一次这是性能提升的关键$handler是静态闭包每个请求只调用闭包本身避免每次重建异常必须自行捕获文档特别指出set_exception_handler只有在 Worker 脚本退出时才会触发与直觉不符所以要在循环内拦截并处理gc_collect_cycles()手动回收在响应发送后、等待下一个请求前主动触发降低页面生成过程中 GC 介入的概率$keepRunning返回值frankenphp_handle_request()返回 false 时退出循环进行优雅清理shutdown()。随后用FRANKENPHP_CONFIG环境变量指向该脚本启动docker run \ -e FRANKENPHP_CONFIGworker ./public/index.php \ -v $PWD:/app \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphp4.1 Worker 数量默认 2 个/CPU可显式指定默认情况下每个 CPU 核心启动 2 个 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对应到源码caddy/workerconfig.go中的worker指令还支持更细粒度的 Caddyfile 配置项子指令说明nameWorker 名称默认取文件名fileWorker 脚本路径必填num启动的 Worker 数量max_threads该 Worker 允许的最大线程数超过后不再扩容见 scaling.go 的扩容逻辑env为 Worker 注入额外环境变量可重复指定watch监听的文件/目录模式留空使用默认模式match按请求路径匹配该 Workermax_consecutive_failures最大连续失败次数详见下文4.2 每处理 N 个请求后自动重启MAX_REQUESTSPHP 语言本身并非为常驻进程设计大量旧库和遗留代码存在内存泄漏。在 Worker 模式下应对这类代码的通用做法是处理完一定数量的请求后主动重启 Worker。上面的示例代码已经内置了该能力——通过环境变量MAX_REQUESTS设置上限$maxRequests (int)($_SERVER[MAX_REQUESTS] ?? 0); for ($nbRequests 0; !$maxRequests || $nbRequests $maxRequests; $nbRequests) {当MAX_REQUESTS为 0默认时不限制设置为 N 时Worker 处理完 N 个请求后正常退出由 FrankenPHP 拉起新的 Worker 进程从而周期性回收内存。五、手动重启 WorkerCaddy Admin API除了按文件变化自动重启之外还可以通过 Caddy 的管理 API 中启用了 Admin 端点。发送一条简单的 POST 请求即可curl -X POST http://localhost:2019/frankenphp/workers/restart该端点定义在 caddy/admin.go 中// EXPERIMENTAL: These routes are not yet stable and may change in the future. func (admin FrankenPHPAdmin) Routes() []caddy.AdminRoute { return []caddy.AdminRoute{ { Pattern: /frankenphp/workers/restart, Handler: caddy.AdminHandlerFunc(admin.restartWorkers), }, { Pattern: /frankenphp/threads, Handler: caddy.AdminHandlerFunc(admin.threads), }, } }注意三点该路由仅接受 POST其他方法返回 405成功时响应workers restarted successfully\n对应测试见 caddy/admin_test.go源码注释明确标注EXPERIMENTAL实验性路由在未来版本可能变化同模块还提供/frankenphp/threads端点可查看各线程的运行状态快照。底层实现调用frankenphp.RestartWorkers()见 worker.go它会等待所有 Worker 线程让出控制权后统一重启——源码注释强调所有 Worker 必须同时重启以避免 opcache 重置带来的不一致问题。六、Worker 崩溃处理与指数退避如果 Worker 以非零退出码崩溃FrankenPHP 会按**指数退避exponential backoff**策略重启它。判断逻辑如下若 Worker 存活时间超过上一次退避时长 × 2则不被惩罚直接再次重启若 Worker 在短时间内持续以非零退出码失败例如脚本存在拼写错误FrankenPHP 最终会以错误too many consecutive failures停止。源码实现位于 threadworker.go// wait a bit and try again (exponential backoff) backoffDuration : time.Duration(handler.failureCount*handler.failureCount*100) * time.Millisecond if backoffDuration time.Second { backoffDuration time.Second } handler.failureCount time.Sleep(backoffDuration)即退避时长按失败次数的平方增长1 次失败约 100ms、2 次约 400ms、3 次约 900ms……封顶 1 秒。触发终止的判定if worker.maxConsecutiveFailures 0 startupFailChan ! nil !watcherIsEnabled handler.failureCount worker.maxConsecutiveFailures { startupFailChan - fmt.Errorf(too many consecutive failures: worker %s has not reached frankenphp_handle_request(), worker.fileName) handler.thread.state.Set(state.ShuttingDown) return }6.1 配置 max_consecutive_failures最大连续失败次数可在 Caddyfile 的frankenphp块中配置frankenphp { worker { # ... max_consecutive_failures 10 } }依据 caddy/workerconfig.go默认值为 6设为-1 表示永不因连续失败而终止set to -1 to never panick取值必须 -1否则 Caddyfile 解析直接报错max_consecutive_failures must be -1当--watch监听模式开启时该上限判定会被跳过!watcherIsEnabled因为脚本失败更可能是文件改动所致。七、超全局变量Superglobals的行为PHP 超全局变量$_SERVER、$_ENV、$_GET等在 Worker 模式下遵循以下规则在第一次调用frankenphp_handle_request()之前超全局变量保存的是Worker 脚本自身的值在frankenphp_handle_request()调用期间及之后超全局变量保存的是当前 HTTP 请求的值每次调用都会用新请求的值覆盖它们。如果要在回调内访问 Worker 脚本自己的超全局变量必须在第一次调用前复制并导入闭包作用域?php // Copia il superglobale $_SERVER del worker prima della prima chiamata a frankenphp_handle_request() $workerServer $_SERVER; $handler static function () use ($workerServer) { var_dump($_SERVER); // $_SERVER legato alla richiesta var_dump($workerServer); // $_SERVER dello script worker }; // ...7.1 重要差异$_ENV 不会被重置大部分超全局变量$_GET、$_POST、$_COOKIE、$_FILES、$_SERVER、$_REQUEST会在请求之间自动重置。但$_ENV目前不会在请求之间重置。这意味着某次请求中对$_ENV的任何修改都会持续存在并对同一 Worker 线程后续处理的请求可见因此不要在$_ENV中存放敏感数据或请求专属数据。这是当前实现的行为约束原文档明确标注attualmente即目前未来版本可能改变写 Worker 时请务必留意。八、状态持久化Worker 模式的性能之源与副作用之源由于 Worker 模式让 PHP 进程在请求之间保持存活以下状态会跨请求持续存在静态变量函数/方法内以static声明的变量其值跨请求保留类静态属性类上的static属性跨请求保留全局变量Worker 全局作用域中的变量跨请求保留内存缓存请求处理器外部存储于内存中的任何数据数组、对象都会保留。这是设计使然也正是 Worker 模式快的原因——但要警惕副作用。官方文档给出了经典反例?php function getCounter(): int { static $count 0; return $count; // Incrementa tra le richieste! } $handler static function () { echo getCounter(); // 1, 2, 3, ... per ogni richiesta su questo thread }; while (\frankenphp_handle_request($handler)) { // ... }这段代码会按请求依次输出 1、2、3……因为$count在请求间持续递增。编写 Worker 时务必在每个请求之间重置一切请求专属状态。Symfony 与 Laravel Octane 等框架会自动恢复大部分状态但某些服务仍可能需要手动重置Symfony 中持有请求专属状态的服务应实现Symfony\Contracts\Service\ResetInterface以便内核在请求之间调用其reset()方法。九、进一步阅读官方 Worker 文档原文docs/it/worker.md另有 docs/worker.md 等语言版本文件监听与 Caddyfile 配置docs/it/config.md热重载docs/it/hot-reload.mdSymfony 集成docs/it/symfony.mdLaravel Octane 集成docs/it/laravel.mdWorker 核心实现worker.go、threadworker.goCaddyfileworker指令解析caddy/workerconfig.go管理 API 重启端点caddy/admin.go 及其测试 caddy/admin_test.go指标frankenphp_ready_workers等可辅助观测 Worker 就绪状态metrics.go【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →