PHP "真異步" TrueAsync SAPI 與 NGINX Unit 集成
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
調用 $response->write($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- 請求 URIgetRequestContext(): ?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 提供了一個值得認真考慮的選擇。它不需要改變太多現有代碼結構,卻能帶來性能上的顯著提升。

浙公網安備 33010602011771號