PHP "真異(yi)步(bu)" TrueAsync SAPI 與 NGINX Unit 集成
PHP "真異步" TrueAsync SAPI 與 NGINX Unit 集成
現在的(de) Web 開發(fa)和過去最大(da)的(de)區(qu)別是什(shen)么?一(yi)句話:沒(mei)人(ren)再愿意等服務器響應(ying)了。
七八年前,甚至更早的時候(hou),模塊(kuai)加(jia)載、組件打包、腳本解釋、數據(ju)庫查詢——這些步驟慢一(yi)點,對業(ye)務(wu)和用(yong)戶也不會造成太大影響。
現(xian)在不一樣了。Web 開(kai)發(fa)的(de)(de)核心已經變(bian)成(cheng)了最大化(hua)服務器(qi)響應速(su)度。這種轉變(bian)來自網速(su)的(de)(de)提升和單頁應用(yong)(SPA)的(de)(de)普及。對(dui)后端來說,就(jiu)是要能處(chu)理海(hai)量的(de)(de)快速(su)請求,還得把負載分配好。
經典(dian)的雙池架(jia)構(請(qing)求(qiu) worker + 任務 worker)不(bu)是(shi)憑空出現的。
一(yi)個(ge)(ge)請(qing)求(qiu)一(yi)個(ge)(ge)進(jin)程的模型(xing),根本(ben)扛不住(zhu)大批量(liang)的輕量(liang)請(qing)求(qiu)。該上并(bing)發(fa)了(le)——一(yi)個(ge)(ge)進(jin)程同(tong)時處理多個(ge)(ge)請(qing)求(qiu)。
并發處(chu)理帶來了(le)新要(yao)求:服務(wu)器代碼要(yao)盡可(ke)能貼近(jin)業(ye)務(wu)邏輯(ji)。以前不(bu)(bu)是(shi)這(zhe)樣的。以前可(ke)以用 CGI 或(huo) FPM 把 Web 服務(wu)器和(he)腳本文件分得清清楚(chu)楚(chu),很優雅。現在這(zhe)招不(bu)(bu)好使了(le)。
所以現在的方案要么就是把組件集成得盡量緊密,要么干脆把 Web 服務器當內部模塊嵌進去。NGINX Unit 就是這(zhe)么(me)干的——它把 JavaScript、Python、Go 這(zhe)些語(yu)言直(zhi)接(jie)嵌到 worker 模塊里。PHP 也有模塊,但一(yi)直(zhi)以來,PHP 在這(zhe)種直(zhi)接(jie)集(ji)成里沒撈到什么(me)好處,因為還是一(yi)個 worker 只能處理(li)一(yi)個請求。
集成特性
架構
這個集成分三層:
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 會激(ji)活(huo) TrueAsync SAPI,而不是(shi)標準的(de) PHP SAPI。
加載配置
curl -X PUT --data-binary @unit-config.json \
--unix-socket /tmp/unit/control.unit.sock \
//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 參數必須加(jia),用來加(jia)載 PHP 模(mo)塊。
查看日志
tail -f /tmp/unit/unit.log
測試
curl //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 //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 //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 真正擁有了異步處理能(neng)力(li)。通過(guo)(guo)協程機(ji)制,單個進程可以(yi)同時(shi)處理多個請求,這在(zai)過(guo)(guo)去是無法想(xiang)象的。
對(dui)于 PHP 生態來說,這是(shi)一個(ge)(ge)重(zhong)要的(de)轉(zhuan)折點。傳統的(de) PHP-FPM 模式下,每個(ge)(ge)請(qing)求獨占一個(ge)(ge)進程,在高并發場景下資源(yuan)消(xiao)耗(hao)巨(ju)大。現(xian)在有了 TrueAsync,PHP 可以(yi)像 Node.js、Go 那樣高效處理并發請(qing)求,同時保持語言本身(shen)的(de)簡潔性。
雖然目前還有(you)一些(xie)功能(neng)在開發中,比如完整的請求頭(tou)支持、POST 數據解析、WebSocket 等,但現有(you)的功能(neng)已(yi)經(jing)足(zu)夠構建(jian)高性能(neng)的 API 服務。非(fei)阻(zu)塞 I/O、協(xie)程調度、事件循環——這(zhe)些(xie)核心機制都已(yi)經(jing)就位。
對于需要(yao)處理(li)高并發請求(qiu)的 PHP 應(ying)用,特別是 API 服務、微(wei)服務架構(gou),NGINX Unit TrueAsync 提(ti)供(gong)了一個值(zhi)得認真考(kao)慮(lv)的選(xuan)擇(ze)。它不(bu)需要(yao)改變太多現有代碼(ma)結構(gou),卻能(neng)帶來性能(neng)上的顯著提(ti)升。
