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) 时:

  1. 数据通过 nxt_unit_response_write_nb() 发送
  2. 缓冲区满了,剩余数据进 drain_queue
  3. 缓冲区空出来,触发 shm_ack_handler
  4. 异步写入,不阻塞协程

配置

unit-config.json

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。

加载配置

bash 复制代码
curl -X PUT --data-binary @unit-config.json \
  --unix-socket /tmp/unit/control.unit.sock \
  http://localhost/config

entrypoint.php

基本结构:

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() 必须调用,完成请求

生命周期

php 复制代码
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

bash 复制代码
./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 模块。

查看日志

bash 复制代码
tail -f /tmp/unit/unit.log

测试

bash 复制代码
curl http://127.0.0.1:8080/

响应:

json 复制代码
{
    "message": "Hello from NginxUnit TrueAsync HttpServer!",
    "method": "GET",
    "uri": "/",
    "timestamp": "2025-10-04 15:30:00"
}

负载测试

bash 复制代码
wrk -t4 -c100 -d30s http://127.0.0.1:8080/

调试

GDB

bash 复制代码
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

实用命令

bash 复制代码
# 停止所有 NGINX Unit 进程
pkill -9 unitd

# 检查控制套接字
ls -la /tmp/unit/control.unit.sock

# 获取当前配置
curl --unix-socket /tmp/unit/control.unit.sock http://localhost/config

内部实现

初始化

  1. nxt_php_extension_init()NginxUnit 命名空间注册类
  2. worker 启动时加载 entrypoint.php
  3. HttpServer::onRequest() 把回调存到 nxt_php_request_callback

请求处理

  1. NGINX Unit 调用 nxt_php_request_handler(req)
  2. 创建协程:zend_async_coroutine_create(nxt_php_request_coroutine_entry)
  3. 协程指针存到 req
  4. 协程加入激活队列
  5. 控制权回到事件循环 nxt_unit_run()

协程激活

  1. 事件循环调用 nxt_unit_response_buf_alloc 回调
  2. 回调通过 zend_async_coroutine_activate() 激活协程
  3. 执行 nxt_php_request_coroutine_entry()
  4. 创建 PHP Request/Response 对象
  5. 调用用户回调
  6. response->end() 后协程结束

异步发送

  1. response->write()nxt_unit_response_write_nb()
  2. 没发完,剩下的进 drain_queue
  3. 缓冲区空了,触发 shm_ack_handler()
  4. 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 提供了一个值得认真考虑的选择。它不需要改变太多现有代码结构,却能带来性能上的显著提升。

相关推荐
电商API&Tina17 小时前
京东 API 数据采集接口接入与行业分析
运维·服务器·网络·数据库·django·php
短剑重铸之日17 小时前
《SpringBoot4.0初识》第一篇:前瞻与思想
java·开发语言·后端·spring·springboot4.0
it_czz17 小时前
LangSmith vs LangFlow vs LangGraph Studio 可视化配置方案对比
后端
蓝色王者17 小时前
springboot 2.6.13 整合flowable6.8.1
java·spring boot·后端
花哥码天下18 小时前
apifox登录后设置token到环境变量
java·后端
hashiqimiya19 小时前
springboot事务触发滚动与不滚蛋
java·spring boot·后端
TeamDev19 小时前
基于 Angular UI 的 C# 桌面应用
前端·后端·angular.js
PPPHUANG19 小时前
一次 CompletableFuture 误用,如何耗尽 IO 线程池并拖垮整个系统
java·后端·代码规范
用户83562907805120 小时前
用Python轻松管理Word页脚:批量处理与多节文档技巧
后端·python
想用offer打牌20 小时前
一站式了解Spring AI Alibaba的流式输出
java·人工智能·后端