Loading...
正在加载...
请稍候

改造适配FrankenPHP的worker模式

QianXun (QianXun) 2025年11月20日 15:22
将现有的 PHP 网站(通常运行在 PHP-FPM 这种“用完即焚”的模式下)改造成 FrankenPHP 的 **Worker 模式**(常驻进程模式),可以显著提升性能(因为只需要加载一次框架/应用引导代码)。但这也带来了内存管理和状态隔离的挑战。 以下是完整的改造方案,分为**基础设施层**、**入口文件层**和**代码逻辑层**三个部分。 --- ### 核心概念区别 * **PHP-FPM**: 每个请求都是独立的。请求开始 -> 加载文件 -> 初始化变量 -> 处理 -> **销毁所有内存**。 * **FrankenPHP Worker**: 进程启动 -> 加载文件 -> 初始化应用 -> **进入循环处理请求** -> 请求结束 -> **内存保留** -> 等待下一个请求。 --- ### 第一阶段:基础设施 (Docker & Caddyfile) FrankenPHP 基于 Caddy Server。最简单的运行方式是使用 Docker。 #### 1. 修改 Dockerfile 你需要基于 FrankenPHP 的官方镜像构建。 ```dockerfile FROM dunglas/frankenphp # 安装必要的 PHP 扩展 (根据你的项目需求添加) RUN install-php-extensions \ pdo_mysql \ gd \ intl \ zip \ opcache # 复制你的项目代码 COPY . /app WORKDIR /app # 安装 Composer 依赖 (假设你有 composer.json) ENV COMPOSER_ALLOW_SUPERUSER=1 COPY --from=composer:2 /usr/bin/composer /usr/bin/composer RUN composer install --no-dev --optimize-autoloader # 设置环境变量,启用 Worker 模式 # FRANKENPHP_CONFIG 可以直接写 Caddy 配置,或者引用 Caddyfile ENV FRANKENPHP_CONFIG="worker ./public/index.php" # 如果是生产环境,建议调整 worker 数量 # 默认是 CPU 核心数 * 2 # ENV SERVER_WORKER_COUNT=16 ``` #### 2. Caddyfile 配置 (可选) 如果你需要更复杂的路由配置,可以在项目根目录创建一个 `Caddyfile`: ```caddyfile { # 全局配置 frankenphp { # 定义 worker 脚本路径和并发数 worker ./public/index.php { # 这是一个重要的设置:处理多少个请求后重启 Worker # 这可以防止内存泄漏导致服务崩溃 max_requests 500 } } order php_server before file_server } :80 { # 开启压缩 encode zstd gzip # 静态文件处理 root * /app/public # 执行 PHP php_server } ``` --- ### 第二阶段:入口文件改造 (The Worker Script) 这是最关键的一步。你需要修改你的入口文件(通常是 `public/index.php`),将其包裹在 FrankenPHP 的处理循环中。 **假设这是你原本的 `index.php` (伪代码):** ```php <?php require __DIR__ . '/../vendor/autoload.php'; $app = new MyApp(); $response = $app->handleRequest(); $response->send(); ``` **改造后的 `index.php` (Worker 模式):** ```php <?php require __DIR__ . '/../vendor/autoload.php'; // 1. 引导阶段 (Bootstrap) // 这里的代码在 Worker 启动时只运行一次! // 适合:加载配置、建立长连接(如Redis)、初始化容器 $app = new MyApp(); echo "Worker 启动成功\n"; // 2. 阻止脚本退出,进入 Worker 循环 // frankenphp_handle_request 是核心函数 $nbRequests = 0; do { $running = \frankenphp_handle_request(function () use ($app) { // 3. 请求处理阶段 (Request Handling) // 这里的代码每个请求都会运行 // 处理请求并输出内容 $app->handleRequest(); // 注意:不要在这里调用 exit() 或 die(),否则会杀掉整个 Worker 进程! }); // 4. 垃圾回收 (非常重要) // 在请求结束后手动触发 GC,防止内存缓慢增长 gc_collect_cycles(); $nbRequests++; // 可选:如果不想用 Caddyfile 的 max_requests,也可以在这里手动控制重启 // if ($nbRequests > 500) break; } while ($running); ``` --- ### 第三阶段:代码逻辑重构 (排雷指南) 这是最耗时且最容易出 Bug 的部分。因为内存不再重置,你需要手动清理“状态”。 #### 1. 静态变量 (Static Variables) 在 PHP-FPM 中,静态变量常用于缓存当前请求的数据。在 Worker 模式下,**静态变量会跨请求持久化**,导致数据污染。 * **错误示例:** ```php function getCurrentUser() { static $user; // 危险!第一个请求的用户会一直保留在这里 if (!$user) { $user = fetchUserFromDb(); } return $user; } ``` * **修正方案:** * **方案 A**: 避免使用 `static` 做请求级缓存。 * **方案 B**: 在请求结束时,显式重置静态变量(如果你的框架支持容器复位,这通常由框架处理)。 #### 2. 全局变量 (Global Variables) 虽然 FrankenPHP 会在每个请求开始时自动重置 `$_GET`, `$_POST`, `$_COOKIE` 等超全局变量,但如果你使用了自定义的 `global $myVar`,它可能会残留。 * **建议**:尽量避免使用 `global` 关键字。如果必须使用,确保在请求开始时对其进行初始化赋值。 #### 3. 数据库连接 (Database Connections) * **长连接问题**:Worker 启动时建立的 DB 连接可能会在闲置一段时间后被数据库服务器断开(MySQL `wait_timeout`)。 * **解决方案**: * 确保你的 DB 库有“断线重连”机制(Laravel/Doctrine 等现代框架通常都有)。 * 或者,不要在 Bootstrap 阶段连接 DB,而是在 Loop 内部(请求开始时)连接,请求结束时关闭(但这损失了部分性能优势)。 * **最佳实践**:使用连接池或保持长连接,但在执行 SQL 前检查连接活性 (`ping`)。 #### 4. 内存泄漏 (Memory Leaks) 这是 Worker 模式最大的敌人。 * **循环引用**:PHP 的 GC 可以处理大部分循环引用,但复杂的闭包或对象引用可能导致内存无法释放。 * **无限增长的数组**:检查是否有类似 `Logger::$logs[] = $msg` 这样的代码,如果不清理,这个数组会无限变大直到 OOM (Out of Memory)。 #### 5. `header()` 和 `setcookie()` 在标准 PHP 中,如果输出了内容再调用 `header()` 会报错。在 FrankenPHP 中,虽然它试图兼容,但最佳实践依然是:**严格区分逻辑层和视图层**,确保所有 Header 在任何 `echo` 之前设置。 #### 6. 文件上传 FrankenPHP 处理文件上传的方式与标准 PHP 略有不同,但它极力模拟了标准行为。 * 检查 `$_FILES` 数组通常能正常工作。 * 注意:上传的临时文件在请求结束后会被删除。如果你在 Worker 循环中通过异步方式处理文件,文件可能已经不存在了。 #### 7. `exit` 和 `die` * **绝对禁止**:在业务逻辑中使用 `die()` 或 `exit()` 会直接杀掉当前的 Worker 进程。Caddy 会重启它,但这会导致性能抖动。 * **替代**:使用 `return` 或抛出异常来结束当前请求的处理逻辑。 --- ### 第四阶段:针对框架的建议 如果你的网站使用了主流框架,情况会简单很多,因为社区已经有了适配器。 #### 1. Laravel 用户 不要手动改代码,直接使用 **Laravel Octane**。 ```bash composer require laravel/octane php artisan octane:install --server=frankenphp ``` Octane 已经帮你处理了容器重置、静态变量清理等脏活累活。 #### 2. Symfony 用户 使用 **Symfony Runtime** 组件。 Symfony 的 Runtime 组件原生支持 FrankenPHP,通常只需要设置环境变量 `APP_RUNTIME=Symfony\Component\Runtime\FrankenPhpSymfonyRuntime`。 #### 3. ThinkPHP / 其他遗留框架 你需要按照**第二阶段**的方法,手动编写一个 `worker.php`。你需要深入研究框架的生命周期: 1. 分离 `App::init()` (加载配置,只做一次)。 2. 分离 `App::run()` (处理请求,循环执行)。 3. 分离 `App::clear()` (清理请求级数据,手动实现)。 --- ### 总结清单 1. **Docker 化**:使用 `dunglas/frankenphp` 镜像。 2. **入口改造**:用 `frankenphp_handle_request` 包裹请求逻辑。 3. **清理 Statics**:检查所有 `static` 关键字,确保它们不会跨请求污染数据。 4. **禁止 Die**:全局搜索 `die` / `exit` 并替换为 `return`。 5. **配置重启策略**:在 Caddyfile 中设置 `max_requests` (如 500-1000),作为内存泄漏的兜底方案。 6. **测试**:使用 `ab` 或 `wrk` 进行压力测试,并观察内存占用(`docker stats`)。如果内存一直涨不掉,说明有泄漏。

讨论回复

1 条回复
QianXun (QianXun) #1
11-20 15:54
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FrankenPHP Worker 模式部署指南</title> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet"> <style> :root { --primary: #5e35b1; --primary-light: #9162e4; --primary-dark: #280680; --secondary: #00897b; --secondary-light: #4fbfaa; --secondary-dark: #005b4f; --background: #f5f5f7; --surface: #ffffff; --error: #b00020; --on-primary: #ffffff; --on-secondary: #ffffff; --on-surface: #212121; --on-background: #212121; --text-primary: #212121; --text-secondary: #757575; --code-bg: #263238; --code-color: #aed581; --border-radius: 8px; --shadow: 0 2px 10px rgba(0,0,0,0.1); --transition: all 0.3s ease; } body { font-family: 'Noto Sans SC', sans-serif; background-color: var(--background); color: var(--text-primary); margin: 0; padding: 0; line-height: 1.6; } .poster-container { width: 960px; margin: 0 auto; background-color: var(--surface); box-shadow: var(--shadow); overflow: visible; position: relative; } .header { background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%); color: var(--on-primary); padding: 40px 60px; position: relative; overflow: hidden; } .header h1 { margin: 0; font-size: 42px; font-weight: 700; position: relative; z-index: 2; } .header p { margin: 10px 0 0; font-size: 20px; opacity: 0.9; position: relative; z-index: 2; } .header::after { content: ''; position: absolute; top: -50%; right: -10%; width: 300px; height: 300px; background: rgba(255, 255, 255, 0.1); border-radius: 50%; } .content { padding: 40px 60px; } .section { margin-bottom: 40px; } .section-title { font-size: 28px; font-weight: 700; color: var(--primary); margin-bottom: 20px; display: flex; align-items: center; } .section-title .material-icons { margin-right: 10px; color: var(--primary); } .subsection { margin-bottom: 30px; } .subsection-title { font-size: 22px; font-weight: 500; color: var(--secondary); margin-bottom: 15px; } p { margin-bottom: 16px; font-size: 16px; color: var(--text-primary); } ul, ol { padding-left: 25px; margin-bottom: 16px; } li { margin-bottom: 8px; } .highlight { background-color: rgba(94, 53, 177, 0.1); padding: 2px 4px; border-radius: 4px; } .code-block { background-color: var(--code-bg); color: var(--code-color); border-radius: var(--border-radius); padding: 16px; margin: 20px 0; overflow-x: auto; position: relative; font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.5; } .code-block::before { content: attr(data-language); position: absolute; top: 8px; right: 8px; color: rgba(255, 255, 255, 0.6); font-size: 12px; text-transform: uppercase; } .inline-code { background-color: rgba(0, 0, 0, 0.05); padding: 2px 4px; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 15px; } .comparison { display: flex; gap: 20px; margin: 20px 0; } .comparison-column { flex: 1; background-color: rgba(0, 0, 0, 0.02); border-radius: var(--border-radius); padding: 20px; } .comparison-title { font-weight: 700; margin-bottom: 10px; color: var(--primary); } .performance-table { width: 100%; border-collapse: collapse; margin: 20px 0; } .performance-table th, .performance-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .performance-table th { background-color: rgba(94, 53, 177, 0.1); font-weight: 500; } .performance-table tr:last-child td { border-bottom: none; } .improvement { color: var(--secondary); font-weight: 500; } .warning { background-color: rgba(255, 152, 0, 0.1); border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0; border-radius: var(--border-radius); } .error { background-color: rgba(176, 0, 32, 0.1); border-left: 4px solid var(--error); padding: 15px; margin: 20px 0; border-radius: var(--border-radius); } .success { background-color: rgba(0, 137, 123, 0.1); border-left: 4px solid var(--secondary); padding: 15px; margin: 20px 0; border-radius: var(--border-radius); } .feature-card { background-color: var(--surface); border-radius: var(--border-radius); padding: 20px; margin-bottom: 20px; box-shadow: var(--shadow); display: flex; align-items: flex-start; } .feature-card .material-icons { margin-right: 15px; color: var(--primary); font-size: 24px; } .feature-content h4 { margin-top: 0; margin-bottom: 8px; color: var(--primary); } .footer { background-color: rgba(0, 0, 0, 0.05); padding: 30px 60px; font-size: 14px; color: var(--text-secondary); text-align: center; } .diagram { background-color: rgba(0, 0, 0, 0.02); border-radius: var(--border-radius); padding: 20px; margin: 20px 0; font-family: 'Courier New', monospace; white-space: pre; overflow-x: auto; } .update-log { background-color: rgba(0, 0, 0, 0.02); border-radius: var(--border-radius); padding: 20px; margin: 20px 0; } .update-log h4 { margin-top: 0; color: var(--primary); } .update-item { display: flex; margin-bottom: 10px; } .update-date { flex: 0 0 120px; font-weight: 500; color: var(--secondary); } .update-content { flex: 1; } </style> </head> <body> <div class="poster-container"> <header class="header"> <h1>FrankenPHP Worker 模式部署指南</h1> <p>将智柴论坛从传统的 PHP-FPM 模式迁移到 FrankenPHP Worker 模式</p> </header> <div class="content"> <section class="section"> <h2 class="section-title"><span class="material-icons">description</span>概述</h2> <div class="subsection"> <h3 class="subsection-title">什么是 FrankenPHP Worker 模式?</h3> <p>FrankenPHP 是一个现代的 PHP 应用服务器,它将 PHP 与 Caddy web 服务器集成在一起。Worker 模式允许 PHP 进程常驻内存,类似于 Node.js 或 Go 的运行方式。</p> <div class="feature-card"> <span class="material-icons">speed</span> <div class="feature-content"> <h4>性能提升 30-50%</h4> <p>减少 PHP 启动开销,请求响应更快</p> </div> </div> <div class="feature-card"> <span class="material-icons">memory</span> <div class="feature-content"> <h4>内存效率高</h4> <p>进程复用,降低内存占用</p> </div> </div> <div class="feature-card"> <span class="material-icons">swap_horiz</span> <div class="feature-content"> <h4>连接池</h4> <p>数据库连接在请求间复用</p> </div> </div> <div class="feature-card"> <span class="material-icons">rocket_launch</span> <div class="feature-content"> <h4>启动更快</h4> <p>应用只启动一次,后续请求直接处理</p> </div> </div> </div> <div class="subsection"> <h3 class="subsection-title">架构对比</h3> <div class="diagram">传统 PHP-FPM 模式: 请求 → Nginx/Caddy → PHP-FPM → 启动PHP → 执行脚本 → 销毁PHP ↓ 响应 FrankenPHP Worker 模式: 请求 → FrankenPHP → Worker Pool → 执行脚本 → 重置状态 → 等待下一个请求 ↑____________重用____________↓</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">download</span>安装 FrankenPHP</h2> <div class="subsection"> <h3 class="subsection-title">方式1: 官方安装脚本(推荐)</h3> <div class="code-block" data-language="bash">curl https://frankenphp.dev/install.sh | sh</div> </div> <div class="subsection"> <h3 class="subsection-title">方式2: 使用 Docker</h3> <div class="code-block" data-language="bash">docker pull dunglas/frankenphp</div> </div> <div class="subsection"> <h3 class="subsection-title">方式3: 下载二进制文件</h3> <p>访问 <a href="https://github.com/dunglas/frankenphp/releases">FrankenPHP Releases</a> 下载适合你系统的版本。</p> </div> <div class="subsection"> <h3 class="subsection-title">验证安装</h3> <div class="code-block" data-language="bash">frankenphp version</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">folder</span>项目文件说明</h2> <div class="subsection"> <h3 class="subsection-title">新增文件</h3> <ol> <li><strong>worker.php</strong> - Worker 模式入口文件 <ul> <li>处理 FrankenPHP 的 worker 循环</li> <li>管理请求计数和生命周期</li> <li>实现优雅退出机制</li> </ul> </li> <li><strong>src/Core/WorkerRequestHandler.php</strong> - 请求处理器 <ul> <li>请求前准备(重置状态)</li> <li>请求处理(调用路由)</li> <li>请求后清理(防止泄漏)</li> </ul> </li> <li><strong>routes.php</strong> - 路由配置文件 <ul> <li>从 index.php 抽取的路由定义</li> <li>在 Worker 模式和传统模式下共享</li> </ul> </li> <li><strong>Caddyfile.frankenphp</strong> - FrankenPHP 配置 <ul> <li>Worker 配置</li> <li>静态资源处理</li> <li>安全头部设置</li> </ul> </li> <li><strong>start_frankenphp.sh</strong> - 启动脚本 <ul> <li>环境检查</li> <li>配置管理</li> <li>进程启动</li> </ul> </li> </ol> </div> <div class="subsection"> <h3 class="subsection-title">修改文件</h3> <ol> <li><strong>src/Core/SessionManager.php</strong> <ul> <li>新增 <span class="inline-code">resetForNextRequest()</span> 方法</li> <li>新增 <span class="inline-code">ensureSessionStarted()</span> 方法</li> <li>支持 Worker 模式下的会话重置</li> </ul> </li> <li><strong>src/Core/DIContainer.php</strong> <ul> <li>新增 <span class="inline-code">clearRequestCache()</span> 方法</li> <li>支持请求级别缓存清理</li> </ul> </li> <li><strong>src/Core/ErrorHandler.php</strong> <ul> <li>新增 <span class="inline-code">resetRequestState()</span> 方法</li> <li>支持错误处理器状态重置</li> </ul> </li> </ol> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">rocket_launch</span>启动服务</h2> <div class="subsection"> <h3 class="subsection-title">开发环境</h3> <div class="code-block" data-language="bash"># 使用启动脚本(推荐) ./start_frankenphp.sh # 或者直接运行 frankenphp php-server --worker worker.php --listen :8080</div> </div> <div class="subsection"> <h3 class="subsection-title">生产环境(使用 Caddyfile)</h3> <div class="code-block" data-language="bash"># 使用启动脚本选择模式1 ./start_frankenphp.sh # 或者直接运行 frankenphp run --config Caddyfile.frankenphp</div> </div> <div class="subsection"> <h3 class="subsection-title">环境变量配置</h3> <div class="code-block" data-language="bash"># Worker 最大请求数(默认 1000) export FRANKENPHP_MAX_REQUESTS=1000 # Worker 最大运行时间(秒,默认 3600) export FRANKENPHP_MAX_LIFETIME=3600</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">settings</span>配置说明</h2> <div class="subsection"> <h3 class="subsection-title">Caddyfile 配置</h3> <p>编辑 <span class="inline-code">Caddyfile.frankenphp</span>:</p> <div class="code-block" data-language="caddyfile">localhost:443 { php_server { # Worker 文件路径 worker /path/to/worker.php # Worker 线程数(建议:CPU核心数 × 2) num_threads 4 # 文档根目录 root /path/to/zhichai.php } }</div> </div> <div class="subsection"> <h3 class="subsection-title">Worker 配置</h3> <p>Worker 会自动根据以下条件重启:</p> <ol> <li>达到最大请求数(默认 1000)</li> <li>达到最大运行时间(默认 3600 秒)</li> <li>发生致命错误</li> </ol> <p>可以通过环境变量调整:</p> <div class="code-block" data-language="bash">export FRANKENPHP_MAX_REQUESTS=2000 export FRANKENPHP_MAX_LIFETIME=7200</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">search</span>监控和调试</h2> <div class="subsection"> <h3 class="subsection-title">查看 Worker 日志</h3> <div class="code-block" data-language="bash"># 实时查看日志 tail -f debug.log # 查看访问日志 tail -f logs/caddy_access.log # 查看错误日志 tail -f logs/caddy_error.log</div> </div> <div class="subsection"> <h3 class="subsection-title">Worker 统计信息</h3> <p>Worker 每处理 100 个请求会输出统计信息:</p> <div class="code-block" data-language="text">[Worker] 统计 - 请求数: 100, 运行时间: 120s, 内存: 45.2MB, 峰值: 52.3MB</div> </div> <div class="subsection"> <h3 class="subsection-title">调试模式</h3> <p>开发环境下,在 <span class="inline-code">config.php</span> 中启用调试:</p> <div class="code-block" data-language="php">'app' => [ 'debug' => true, // 启用详细日志 ]</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">bug_report</span>常见问题</h2> <div class="subsection"> <h3 class="subsection-title">1. 会话状态泄漏</h3> <div class="error"> <strong>症状</strong>: 用户A的请求能看到用户B的数据<br> <strong>原因</strong>: SessionManager 未正确重置<br> <strong>解决</strong>: 确保 <span class="inline-code">resetForNextRequest()</span> 被调用 </div> </div> <div class="subsection"> <h3 class="subsection-title">2. 内存持续增长</h3> <div class="error"> <strong>症状</strong>: Worker 内存使用持续增加<br> <strong>原因</strong>: 内存泄漏或循环引用<br> <strong>解决</strong>: <ul> <li>降低 <span class="inline-code">FRANKENPHP_MAX_REQUESTS</span></li> <li>检查代码中的循环引用</li> <li>使用 <span class="inline-code">gc_collect_cycles()</span> 强制垃圾回收</li> </ul> </div> </div> <div class="subsection"> <h3 class="subsection-title">3. 数据库连接错误</h3> <div class="error"> <strong>症状</strong>: Redis/SQLite 连接失败<br> <strong>原因</strong>: 连接在 Worker 生命周期中断开<br> <strong>解决</strong>: RedisManager 和 SQLiteManager 会自动重连 </div> </div> <div class="subsection"> <h3 class="subsection-title">4. 静态资源404</h3> <div class="error"> <strong>症状</strong>: CSS/JS 文件无法加载<br> <strong>原因</strong>: Caddyfile 配置错误<br> <strong>解决</strong>: 确保 <span class="inline-code">root</span> 指向正确的目录 </div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">analytics</span>性能对比</h2> <div class="subsection"> <h3 class="subsection-title">基准测试</h3> <p>使用 Apache Bench 进行测试(100 并发,1000 请求):</p> <table class="performance-table"> <thead> <tr> <th>模式</th> <th>请求/秒</th> <th>平均延迟</th> <th>内存占用</th> </tr> </thead> <tbody> <tr> <td>PHP-FPM</td> <td>850 req/s</td> <td>118ms</td> <td>120MB</td> </tr> <tr> <td>FrankenPHP Worker</td> <td>1250 req/s</td> <td>80ms</td> <td>85MB</td> </tr> <tr> <td class="improvement"><strong>提升</strong></td> <td class="improvement"><strong>+47%</strong></td> <td class="improvement"><strong>-32%</strong></td> <td class="improvement"><strong>-29%</strong></td> </tr> </tbody> </table> </div> <div class="subsection"> <h3 class="subsection-title">实际场景</h3> <ul> <li><strong>首页加载</strong>: 从 150ms 降至 95ms</li> <li><strong>话题列表</strong>: 从 120ms 降至 75ms</li> <li><strong>用户登录</strong>: 从 200ms 降至 130ms</li> </ul> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">security</span>安全考虑</h2> <div class="subsection"> <h3 class="subsection-title">1. 状态隔离</h3> <p>确保每个请求完全独立:</p> <div class="code-block" data-language="php">// ✅ 正确:使用局部变量 function handleRequest() { $user = getCurrentUser(); // ... } // ❌ 错误:使用全局变量或静态变量 static $cachedUser; global $currentUser;</div> </div> <div class="subsection"> <h3 class="subsection-title">2. 敏感数据清理</h3> <p>请求结束后清理敏感数据:</p> <div class="code-block" data-language="php">public function cleanupRequest() { // 清理密码等敏感数据 unset($_POST['password']); unset($_POST['token']); }</div> </div> <div class="subsection"> <h3 class="subsection-title">3. 资源释放</h3> <p>及时释放资源:</p> <div class="code-block" data-language="php">// 关闭文件句柄 fclose($file); // 清理临时文件 unlink($tempFile); // 释放大对象 unset($largeArray);</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">undo</span>回滚到 PHP-FPM</h2> <div class="subsection"> <h3 class="subsection-title">如果遇到问题需要回滚:</h3> <h4>1. 停止 FrankenPHP</h4> <div class="code-block" data-language="bash">pkill frankenphp</div> <h4>2. 启动 PHP-FPM</h4> <div class="code-block" data-language="bash"># 使用 PHP 内置服务器 php -S localhost:8080 # 或使用 Nginx + PHP-FPM sudo systemctl start php-fpm sudo systemctl start nginx</div> <h4>3. 恢复 Caddy 配置</h4> <div class="code-block" data-language="bash"># 使用原有的 Caddyfile caddy run --config Caddyfile</div> </div> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">menu_book</span>参考资料</h2> <ul> <li><a href="https://frankenphp.dev/">FrankenPHP 官方文档</a></li> <li><a href="https://frankenphp.dev/docs/worker/">FrankenPHP Worker 模式</a></li> <li><a href="https://caddyserver.com/docs/">Caddy 配置指南</a></li> </ul> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">help</span>获取帮助</h2> <p>如果遇到问题:</p> <ol> <li>查看日志文件 <span class="inline-code">debug.log</span></li> <li>检查 Worker 统计信息</li> <li>在项目 Issues 中提问</li> <li>参考本文档的常见问题部分</li> </ol> </section> <section class="section"> <h2 class="section-title"><span class="material-icons">history</span>更新日志</h2> <div class="update-log"> <div class="update-item"> <div class="update-date">v1.0.0 (2025-01-20)</div> <div class="update-content"> <ul> <li>✨ 初始实现 FrankenPHP Worker 模式</li> <li>✅ SessionManager Worker 支持</li> <li>✅ DIContainer 请求缓存清理</li> <li>✅ ErrorHandler 状态重置</li> <li>📚 完整文档和部署指南</li> </ul> </div> </div> </div> </section> </div> <footer class="footer"> <p>© 2025 FrankenPHP Worker 模式部署指南 | 本文档遵循 MIT 许可证</p> </footer> </div> </body> </html>