编程 FrankenPHP worker 模式:bootstrap 只跑一次,跨请求状态得自己管

2026-09-29 21:31:24

FrankenPHP worker 模式:bootstrap 只跑一次,跨请求状态得自己管

项目信息:

  • 官网:https://frankenphp.dev
  • 仓库:https://github.com/dunglas/frankenphp
  • worker 文档:https://frankenphp.dev/zh/docs/worker/

使用 FrankenPHP workers 时,应用只启动一次并常驻内存,FrankenPHP 在几毫秒内处理传入请求。代价也很直接:常驻内存意味着跨请求状态会保留下来,需要主动管理。

启动 worker 脚本

Docker

把 FRANKENPHP_CONFIG 环境变量设置为 worker /path/to/your/worker/script.php:

docker run \
    -e FRANKENPHP_CONFIG="worker /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.php

如果 PHP 应用已嵌入到二进制文件中,可以在应用根目录添加自定义 Caddyfile,它会被自动使用。还可以用 --watch 选项在文件更改时重启 worker:

frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"

该功能通常与热重载结合使用。

Symfony 与 Laravel Octane 集成

Symfony Runtime 部分仅在 Symfony 7.4 之前是必需的,因为 Symfony 7.4 引入了对 FrankenPHP worker 模式的原生支持。安装 runtime/frankenphp-symfony,通过定义 APP_RUNTIME 环境变量启动:

docker run \
    -e FRANKENPHP_CONFIG="worker ./public/index.php" \
    -e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
    -v $PWD:/app \
    -p 80:80 -p 443:443 -p 443:443/udp \
    dunglas/frankenphp

Laravel Octane 请参阅其专门文档。

自定义 worker 脚本

不依赖第三方库的 worker 脚本示例:

<?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();

worker 数量

默认情况下,每个 CPU 启动 2 个 worker。也可以配置要启动的 worker 数量:把 "worker ./public/index.php" 改成 "worker ./public/index.php 42"。

按请求数重启

PHP 最初不是为长时间运行的进程而设计的,仍有许多库和传统代码会泄漏内存。在 worker 模式下使用此类代码的一个解决方法,是在处理一定数量的请求后重启 worker 脚本:前面的 worker 代码片段允许通过设置名为 MAX_REQUESTS 的环境变量来配置要处理的最大请求数(为 0 或未设置表示不限制)。

手动重启 workers

虽然可以在文件更改时重启 workers,但也可以通过 Caddy admin API 优雅地重启所有 workers。如果在 Caddyfile 中启用了 admin,可以通过 POST 请求重启:

curl -X POST http://localhost:2019/frankenphp/workers/restart

Worker 故障与指数退避

如果 worker 脚本因非零退出代码而崩溃,FrankenPHP 将使用指数退避策略重启它。如果 worker 脚本保持运行的时间超过上次退避 × 2,它将不会惩罚 worker 脚本并再次重启它。但是,如果 worker 脚本在短时间内继续以非零退出代码失败(例如脚本中有拼写错误),FrankenPHP 将崩溃并出现错误:too many consecutive failures。可以在 Caddyfile 中用 max_consecutive_failures 选项配置连续失败的次数:

frankenphp {
    worker {
        # ...
        max_consecutive_failures 10
    }
}

超全局变量行为

在第一次调用 frankenphp_handle_request() 之前,超全局变量包含绑定到 worker 脚本本身的值;在调用 frankenphp_handle_request() 期间和之后,超全局变量包含从处理的 HTTP 请求生成的值,每次调用 frankenphp_handle_request() 都会更改超全局变量的值。要在回调内访问 worker 脚本的超全局变量,必须复制它们并将副本导入到回调的作用域中。

大多数超全局变量($_GET、$_POST、$_COOKIE、$_FILES、$_SERVER、$_REQUEST)会在请求间自动重置,但 $_ENV 目前不会在请求间重置,任何在请求期间对 $_ENV 的修改都会持续到后续请求。

跨请求状态持久化风险

worker 模式常驻内存后,以下状态会跨请求保留:函数/方法内的 static 变量、类的静态属性、worker 脚本全局作用域中的变量、handler 之外的内存缓存(数组、对象)。这是 worker 模式高性能的来源,但也是风险:需要主动重置请求相关的状态。

Symfony 7.4+ 与 Laravel Octane 会自动重置大部分状态,但自定义服务仍可能需要在请求之间手动重置:Symfony 中持有请求态的服务应实现 Symfony\Contracts\Service\ResetInterface,由内核在请求间调用重置。

Caddyfile 的 worker 子指令还支持 name、file、num、max_threads、env、watch、match、max_consecutive_failures 等选项,其中 max_consecutive_failures 默认 6,设为 -1 表示永不因连续失败而终止。系统级还支持全局 max_requests 配置,当某个 PHP 线程处理请求数达到上限时会被完整重启(清空内存与状态),期间其他线程继续服务请求;底层通过 ZTS 内存清理实现。

Laravel Octane 使用 FrankenPHP 时请参考 Octane 专门文档。

实际排查时,静态属性或单例里缓存当前用户、租户、数据库连接、请求日志缓冲,以及 handler 外长期持有的数组,最容易在请求之间串状态;请求进入时重置、请求结束后清理,比事后靠 MAX_REQUESTS 重启更可控。

推荐文章

程序员茄子在线接单