<!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>
登录后可参与表态
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!