在當今云原生與微服務大行其道的時代,PHP 應用面臨著「冷啟動延遲高」「進程管理復雜」「性能瓶頸難以突破」等痛點。
FrankenPHP 正是為了解決這些問題而生:它將 Caddy 服務器與 PHP 運行時深度融合,內嵌 Let’s Encrypt 自動 HTTPS、支持 HTTP/2/3、Early Hints 預提示,以及可選的 Worker 模式常駐進程,從根本上消除冷啟動與外部依賴。
這篇文章將帶你逐步了解 FrankenPHP 的核心架構原理,手把手演示從環境安裝、Caddyfile 配置到高并發實戰、微服務嵌入,再到生產部署與監控調優的全流程。
📚 目錄
- 📖 簡介
- 🧱 架構原理
- 🔧 安裝與環境準備
- 💾 二進制安裝
- 🐳 Docker 容器
- 🍺 Homebrew(macOS/Linux)
- 🚀 快速起步
- 🗂? 最簡示例
- 🔁 腳本模式
- ?? 配置詳解
- 🧾 Caddyfile 結構與指令
- 🧪 常用環境變量
- 📝 PHP 運行時配置
- ? 核心特性實操
- 👷 Worker 模式
- 🚦 Early Hints(HTTP 103)
- 🔌 實時推送(WebSocket/SSE)
- 🧩 主流框架集成
- ? Laravel
- 🎼 Symfony
- 📝 WordPress
- 🔗 Go 應用嵌入
- 📈 性能調優與生產部署
- 🔍 性能壓測工具
- 🔐 安全與 TLS
- ?? Kubernetes 部署示例
- 🩺 監控與故障排查
- 📋 日志管理
- 📊 Prometheus 指標
- 🛠? 調試技巧
- 🛒 實戰案例:電商下單服務
- ? 常見問題解答
📖 簡介
FrankenPHP 將 Caddy HTTP 服務器 與 PHP 運行時 深度融合,提供一體化、高性能的 PHP 服務平臺:
- 自動 HTTPS:內置 Let’s Encrypt 證書管理,支持 HTTP/2/3
- Zero Cold Start:Worker 模式常駐進程,避免二次啟動延遲
- 實時能力:原生 WebSocket 和 SSE 支持
- Early Hints:HTTP 103 提示,實現資源預加載
- 單二進制部署:無外部 PHP-FPM、Caddy 進程依賴,簡化運維
適用于中小型團隊快速構建、以及大規模微服務化場景。
🧱 架構原理
Client ? Caddy 核心 ? 嵌入式 PHP SAPI ? Worker 池 ? 應用業務代碼
- Caddy 核心:負責 TLS、路由、靜態資源、HTTP 特性(壓縮、Early Hints)
- 嵌入式 PHP SAPI:將 PHP 引擎及擴展編譯進二進制,通過內存管道與 Caddy 交互
- Worker 池:可選長駐模式,基于線程/進程池調度 PHP 腳本執行,持久化連接池、緩存
🔧 安裝與環境準備
💾 二進制安裝(生產推薦)
curl -fsSL https://frankenphp.dev/install.sh | sh
sudo mv frankenphp /usr/local/bin/
# 驗證安裝
frankenphp --version
- 支持 x86_64 Linux & macOS,內置 PHP 8.4 與常用擴展
- 無額外依賴,直接啟動即可
🐳 Docker 容器(開發/測試)
# docker-compose.yml
version: '3.8'
services:web:image: dunglas/frankenphp:latestvolumes:- ./public:/app/public- ./config:/etc/frankenphpenvironment:SERVER_NAME: 'example.com'ports:- '80:80'- '443:443'
docker-compose up -d
- 將配置目錄掛載到
/etc/frankenphp
,便于自定義php.ini
- 支持 GPU 加速鏡像(另行指定鏡像標簽)
🍺 Homebrew(macOS/Linux)
brew tap dunglas/frankenphp/frankenphp
brew install frankenphp
🚀 快速起步
🗂? 最簡示例
-
項目目錄
my-app/ ├── public/ │ └── index.php └── Caddyfile
-
index.php
<?php echo "Hello, FrankenPHP!";
-
Caddyfile
localhost {php_serverfile_server }
-
啟動
cd my-app frankenphp run
瀏覽器訪問 http://localhost
,顯示 “Hello, FrankenPHP!”。
🔁 腳本模式
-
兼容 PHP CLI:
frankenphp php-cli scripts/cleanup.php --force
-
支持
argv
、STDIN/STDOUT
,與php-cli
用法一致
?? 配置詳解
🧾 Caddyfile 結構與指令
{ # 全局配置servers {http_port 80https_port 443early_hints # HTTP/103enable_full_duplex # 雙工支持}frankenphp {num_threads 4 # 并發線程數max_wait_time 30s # 等待線程超時php_ini { # 覆蓋 php.ini 設置memory_limit 1Gmax_execution_time 60upload_max_filesize 200M}worker { # Worker 模式file public/index.phpnum 6 # Worker 進程數max_jobs 500 # 單個 Worker 最大請求數watch public/**/*.php # 熱重載}}
}example.com {root * /app/publicencode gzip brphp_server {split .phpenv APP_ENV=productiontimeouts {read_timeout 10swrite_timeout 10s}}file_server
}
split .php
:僅.php
請求走 PHP,其余走靜態encode
:開啟 Brotli/Gzip 壓縮timeouts
:精細化控制讀寫超時
🧪 常用環境變量
變量 | 說明 | 默認 |
---|---|---|
SERVER_NAME | TLS 證書域名 | 無 |
CADDY_GLOBAL_OPTIONS | 注入全局 Caddy 配置 | 無 |
FRANKENPHP_CONFIG | 注入 frankenphp 塊配置 | 無 |
PHP_INI_SCAN_DIR | 附加 PHP 配置目錄 | /etc/frankenphp/conf.d |
📝 PHP 運行時配置
在 PHP_INI_SCAN_DIR
目錄中創建 .ini
文件。例如 /etc/frankenphp/conf.d/custom.ini
:
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.logopcache.enable=1
opcache.memory_consumption=512
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 # 生產環境關閉文件監測
? 核心特性實操
👷 Worker 模式
-
優勢:進程常駐,應用啟動僅一次,高并發場景性能穩定。
-
實戰案例:Redis 連接池
worker.php
:<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); while ($req = frankenphp_receive_request()) {$count = $redis->incr('hits');frankenphp_send_response("Hits: {$count}"); }
Caddyfile 配置:
frankenphp {worker {file worker.phpnum 4max_jobs 1000watch worker.php} }
🚦 Early Hints(HTTP 103)
目的:提前通知瀏覽器加載關鍵資源。
<?php
header("Link: </app.css>; rel=preload", false, 103);
header("Link: </app.js>; rel=preload", false, 103);
echo "<html><body>Hello with Early Hints</body></html>";
🔌 實時推送(WebSocket/SSE)
Caddyfile:
servers { enable_full_duplex }
route /ws* {websocketreverse_proxy localhost:9000
}
后端可使用 Swoole 或 Ratchet 實現服務端邏輯。
🧩 主流框架集成
? Laravel
laravel.app {root * /app/publicencode gzip brphp_server {split .phpenv APP_ENV=productionworker {file artisanargs serve --port=8000num 4watch app/**/*.php}}file_server
}
🎼 Symfony
symfony.local {root * /srv/symfony/publicphp_serverfile_server
}
📝 WordPress
wp.local {root * /var/www/htmlphp_serverfile_server
}
🔗 Go 應用嵌入
package mainimport ("net/http""github.com/dunglas/frankenphp"
)func main() {frankenphp.Init(frankenphp.WithNumThreads(4),frankenphp.WithPhpIni(map[string]string{"memory_limit":"256M"}),frankenphp.WithWorkers(frankenphp.WorkerConfig{File:"public/index.php", Num:4, Watch:[]string{"public/**/*.php"}}, ),)mux := http.NewServeMux()mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {frankenphp.ServeHTTP(w, r)}))http.ListenAndServe(":8080", mux)
}
📈 性能調優與生產部署
🔍 性能壓測工具
- wrk:高并發壓力測試
- hey:多維度報告
- autocannon:Node.js 壓測工具
wrk -t8 -c200 -d60s https://example.com
🔐 安全與 TLS
- 自動 HTTPS,無需手動配置證書
- 建議全站強制 HTTPS,并配置 HSTS:
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
?? Kubernetes 部署示例
apiVersion: apps/v1
kind: Deployment
metadata:name: frankenphp
spec:replicas: 3selector:matchLabels:app: frankenphptemplate:metadata:labels:app: frankenphpspec:containers:- name: webimage: dunglas/frankenphp:latestports:- containerPort: 80volumeMounts:- mountPath: /app/publicname: app-codeenv:- name: SERVER_NAMEvalue: "example.com"volumes:- name: app-codeconfigMap:name: my-app-configmap
---
apiVersion: v1
kind: Service
metadata:name: frankenphp-svc
spec:selector:app: frankenphpports:- port: 80targetPort: 80type: LoadBalancer
🩺 監控與故障排查
📋 日志管理
- 內置訪問日志與錯誤日志,默認輸出到 stdout
- 可掛載至文件或收集至 ELK/Fluentd 等集中式系統
- 實時查看:
frankenphp logs -f
📊 Prometheus 指標
在 Caddyfile 中啟用 /metrics
路由:
:80 {metrics /metrics
}
可采集指標包括:
frankenphp_requests_total
frankenphp_active_threads
frankenphp_worker_jobs_total
🛠? 調試技巧
- PHP 錯誤顯示(僅開發環境):
display_errors = On
error_reporting = E_ALL
- 使用 Xdebug 在 IDE 中斷點調試 Worker 進程
🛒 實戰案例:電商下單服務
-
業務需求:記錄用戶下單、庫存扣減、異步發貨通知
-
目錄結構:
ecommerce/ ├── public/index.php ├── src/ │ ├── OrderController.php │ └── Inventory.php ├── worker/notify.php └── Caddyfile
-
核心代碼:
-
OrderController.php
:<?php namespace Src; class OrderController {public function placeOrder($data) {// 驗證、訂單入庫Inventory::deduct($data['sku'], $data['qty']);// 異步通知frankenphp_queue_task('worker/notify.php', $data);return ['status' => 'accepted'];} }
-
notify.php
:<?php while ($job = frankenphp_receive_task()) {// 發送郵件/短信通知Mailer::send($job['user_email'], 'Order Confirmed', ...);frankenphp_finish_task($job); }
-
-
Caddyfile:
{frankenphp {worker {file worker/notify.phpnum 2max_jobs 100}} } ecommerce.local {root * /app/publicphp_serverfile_server }
-
啟動與測試:
frankenphp run
curl -X POST http://ecommerce.local/order -d '{"sku": "ABC", "qty": 1, "user_email": "user@example.com"}'
? 常見問題解答
-
如何優雅重啟 Workers?
修改監控目錄文件,Worker 會自動重載 -
如何擴展 PHP 擴展?
在PHP_INI_SCAN_DIR
下添加extension=xxx.so
-
如何集成 MySQL 持久連接?
在 Worker 中使用 PDO 或 mysqli 常駐連接,多 Worker 共享連接池