PHP “真異步” TrueAsync SAPI 與 NGINX Unit 集成 現在的 Web 開發和過去最大的區別是什麼?一句話:沒人再願意等服務器響應了。
七八年前,甚至更早的時候,模塊加載、組件打包、腳本解釋、數據庫查詢 —— 這些步驟慢一點,對業務和用户也不會造成太大影響。
現在不一樣了。Web 開發的核心已經變成了最大化服務器響應速度。這種轉變來自網速的提升和單頁應用(SPA)的普及。對後端來説,就是要能處理海量的快速請求,還得把負載分配好。
經典的雙池架構(請求 worker + 任務 worker)不是憑空出現的。
一個請求一個進程的模型,根本扛不住大批量的輕量請求。該上併發了 —— 一個進程同時處理多個請求。
併發處理帶來了新要求:服務器代碼要儘可能貼近業務邏輯。以前不是這樣的。以前可以用 CGI 或 FPM 把 Web 服務器和腳本文件分得清清楚楚,很優雅。現在這招不好使了。
所以現在的方案要麼就是把組件集成得儘量緊密,要麼乾脆把 Web 服務器當內部模塊嵌進去。NGINX Unit 就是這麼幹的 —— 它把 JavaScript、Python、Go 這些語言直接嵌到 worker 模塊裏。PHP 也有模塊,但一直以來,PHP 在這種直接集成裏沒撈到什麼好處,因為還是一個 worker 只能處理一個請求。
原文 PHP “真異步” TrueAsync SAPI 與 NGINX Unit 集成
集成特性 架構 這個集成分三層:
C 層(nxt_php_sapi.c, nxt_php_extension.c) 在 PHP 裏註冊 TrueAsync SAPI 給每個請求創建協程 通過 nxt_unit_run() 管理事件循環 通過 nxt_unit_response_write_nb() 實現非阻塞數據傳輸 PHP 擴展層(NginxUnit 命名空間) NginxUnit\Request - 請求對象 NginxUnit\Response - 響應對象,支持非阻塞發送 NginxUnit\HttpServer::onRequest() - 註冊請求處理器 用户代碼(entrypoint.php) 通過 HttpServer::onRequest() 註冊處理器 使用 Request/Response API 完全異步執行 請求流程 HTTP 請求 → NGINX Unit → nxt_php_request_handler() ↓ 創建協程 (zend_async_coroutine_create) ↓ nxt_php_request_coroutine_entry() ↓ 創建 Request/Response 對象 ↓ 調用 entrypoint.php 中的回調函數 ↓ response->write() → nxt_unit_response_write_nb() ↓ response->end() → nxt_unit_request_done() 非阻塞 I/O 調用 data) 時:
數據通過 nxt_unit_response_write_nb() 發送 緩衝區滿了,剩餘數據進 drain_queue 緩衝區空出來,觸發 shm_ack_handler 異步寫入,不阻塞協程 配置 unit-config.json { "applications": { "my-php-async-app": { "type": "php", "async": true, // 啓用 TrueAsync 模式 "processes": 2, // 工作器數量 "entrypoint": "/path/to/entrypoint.php", "working_directory": "/path/to/", "root": "/path/to/" } }, "listeners": { "127.0.0.1:8080": { "pass": "applications/my-php-async-app" } } } 重要:"async": true 會激活 TrueAsync SAPI,而不是標準的 PHP SAPI。
加載配置 curl -X PUT --data-binary @unit-config.json \ --unix-socket /tmp/unit/control.unit.sock \ http://localhost/config entrypoint.php 基本結構:
<?php
use NginxUnit\HttpServer; use NginxUnit\Request; use NginxUnit\Response;
set_time_limit(0);
// 註冊請求處理器 HttpServer::onRequest(static function (Request $request, Response $response) { // 拿請求數據 $method = $request->getMethod(); $uri = $request->getUri();
// 設響應頭
$response->setHeader('Content-Type', 'application/json');
$response->setStatus(200);
// 發數據(非阻塞)
$response->write(json_encode([
'message' => 'Hello from TrueAsync!',
'method' => $method,
'uri' => $uri
]));
// 結束響應
$response->end();
}); API 參考 Request getMethod(): string - HTTP 方法(GET、POST 等) getUri(): string - 請求 URI getRequestContext(): ?mixed - 請求上下文(TODO) getRequestContextParameters(): ?mixed - 上下文參數(TODO) createResponse(): Response - 創建 Response 對象(通常不需要) Response setStatus(int $code): bool - 設置 HTTP 狀態碼 setHeader(string $name, string $value): bool - 添加響應頭 write(string $data): bool - 發送數據(非阻塞操作) end(): bool - 完成響應並釋放資源 注意:
setStatus() 和 setHeader() 要在第一次 write() 之前調用 調用過 write() 後,響應頭就發出去了 end() 必須調用,完成請求 生命週期 HttpServer::onRequest(function (Request $req, Response $resp) { // 1. 響應頭還能改 $resp->setStatus(200); $resp->setHeader('Content-Type', 'text/plain');
// 2. 第一次 write() 把響應頭髮出去了
$resp->write('Hello ');
// 3. 現在響應頭改不了了
// $resp->setHeader() → 報錯!
// 4. 可以繼續寫數據
$resp->write('World!');
// 5. 結束請求(必須調!)
$resp->end();
}); 運行和測試 啓動 NGINX Unit ./build/sbin/unitd \ --no-daemon \ --log /tmp/unit/unit.log \ --state /tmp/unit \ --control unix:/tmp/unit/control.unit.sock \ --pid /tmp/unit/unit.pid \ --modules ./build/lib/unit/modules 重要:--modules 參數必須加,用來加載 PHP 模塊。
查看日誌 tail -f /tmp/unit/unit.log 測試 curl http://127.0.0.1:8080/ 響應:
{ "message": "Hello from NginxUnit TrueAsync HttpServer!", "method": "GET", "uri": "/", "timestamp": "2025-10-04 15:30:00" } 負載測試 wrk -t4 -c100 -d30s http://127.0.0.1:8080/ 調試 GDB gdb ./build/sbin/unitd (gdb) set follow-fork-mode child (gdb) run --no-daemon --log /tmp/unit/unit.log ... 設置斷點 break nxt_php_request_handler break nxt_php_request_coroutine_entry break nxt_unit_response_write_nb 實用命令
停止所有 NGINX Unit 進程
pkill -9 unitd
檢查控制套接字
ls -la /tmp/unit/control.unit.sock
獲取當前配置
curl --unix-socket /tmp/unit/control.unit.sock http://localhost/config 內部實現 初始化 nxt_php_extension_init() 在 NginxUnit 命名空間註冊類 worker 啓動時加載 entrypoint.php HttpServer::onRequest() 把回調存到 nxt_php_request_callback 請求處理 NGINX Unit 調用 nxt_php_request_handler(req) 創建協程:zend_async_coroutine_create(nxt_php_request_coroutine_entry) 協程指針存到 req 協程加入激活隊列 控制權回到事件循環 nxt_unit_run() 協程激活 事件循環調用 nxt_unit_response_buf_alloc 回調 回調通過 zend_async_coroutine_activate() 激活協程 執行 nxt_php_request_coroutine_entry() 創建 PHP Request/Response 對象 調用用户回調 response->end() 後協程結束 異步發送 response->write() → nxt_unit_response_write_nb() 沒發完,剩下的進 drain_queue 緩衝區空了,觸發 shm_ack_handler() shm_ack_handler 繼續寫,需要的話調 end() 未來計劃 實現 Request::getRequestContext() 添加請求頭支持 添加 POST 數據解析 WebSocket 支持 流式響應 總結 NGINX Unit TrueAsync PHP 集成讓 PHP 真正擁有了異步處理能力。通過協程機制,單個進程可以同時處理多個請求,這在過去是無法想象的。
對於 PHP 生態來説,這是一個重要的轉折點。傳統的 PHP-FPM 模式下,每個請求獨佔一個進程,在高併發場景下資源消耗巨大。現在有了 TrueAsync,PHP 可以像 Node.js、Go 那樣高效處理併發請求,同時保持語言本身的簡潔性。
雖然目前還有一些功能在開發中,比如完整的請求頭支持、POST 數據解析、WebSocket 等,但現有的功能已經足夠構建高性能的 API 服務。非阻塞 I/O、協程調度、事件循環 —— 這些核心機制都已經就位。
對於需要處理高併發請求的 PHP 應用,特別是 API 服務、微服務架構,NGINX Unit TrueAsync 提供了一個值得認真考慮的選擇。它不需要改變太多現有代碼結構,卻能帶來性能上的顯著提升。