TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器

TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器

TrueAsync 0.7.0 即将发布,带来线程池及其他若干特性。但其中最引人关注的部分当属 TrueAsync Server:一个直接嵌入 PHP 的高性能 HTTP/1.1、HTTP/2 和 HTTP/3 服务器。无需独立进程,无需反向代理。

一切都在一个线程中

首先也是最重要的一点:TrueAsync Server 是一个"一切都在一个线程中"的服务器。从解析请求到发送响应的整个处理过程,都在单一线程上完成。在这一点上,TrueAsync Server 在以非 PHP 语言实现的 PHP 生态项目中几乎是独一无二的(尽管 Swoole 在基础模式下也运行单个工作进程)。AMPHP 服务器采用了类似的单线程事件循环模型------区别在于 AMPHP 是用 PHP 实现的,而 TrueAsync Server 作为原生扩展嵌入到 PHP 进程中。

"每个线程一个事件循环"的模型本身并不罕见:这正是 NGINX、Envoy、Node.js 以及 Rust 技术栈 Tokio + hyper 的构建方式。核心思想是一个线程从始至终同时持有连接和请求:没有接受线程与工作线程之间的交接,没有锁,没有上下文切换。

优势与代价

这种架构有一个明显的缺点。如果 PHP 虚拟机与 TrueAsync Server 位于同一线程上,而 PHP 虚拟机崩溃------服务器工作进程也会随之崩溃。客户端可能会突然失去连接。如果反应器与 PHP 虚拟机运行在不同的线程上,甚至不同的进程中,架构看起来会更健壮:客户端至少能收到一个错误响应。

缺点到此为止------剩下的都是优势:

  • 无需线程间通信。线程间通信需要复杂的算法,而这些算法永远无法在所有场景下都达到最优:某些类型的网络负载表现良好,另一些则不尽如人意。
  • 简单、可预测的扩展方式。启动第二个工作进程------性能大致翻倍。工作进程通过 setWorkers(N) 启动,内核通过 SO_REUSEPORT 在它们之间分配连接。每个工作进程都是一个独立的事件循环,没有共享状态,没有全局锁。
  • 对服务器的完全、无约束控制。PHP 虚拟机与服务器是一个整体。在另一个线程上管理连接可能复杂得多;当每个操作都在一个线程内时,许多决策都变得更简单。

顺便一提,多工作进程模式在一定程度上抵消了工作进程崩溃的缺点:一个工作进程崩溃不会拖垮其余进程。

为什么用 C 而不是 PHP

PHP 生态中已有不少现代服务器项目。FrankenPHP 基于 Go 语言实现的 Caddy,还有 Rust 语言实现的服务器项目。TrueAsync Server 用 C 编写有充分的理由:

  • 这是将服务器直接嵌入 PHP 的便捷方式------尽可能贴近 PHP 内核。
  • 底层使用了已经成为事实标准的 C 库:nghttp2 用于 HTTP/2,ngtcp2 + nghttp3 用于 HTTP/3,llhttp(Node.js 使用的同一个解析器)用于 HTTP/1.1。
  • 服务器直接链接到 OpenSSL,后者已经是 PHP 构建的一部分。不过,对于 HTTP/3,需要用 3.5 以上版本替换------这是其所需要的 QUIC TLS API 首次出现的版本。
  • 服务器使用了 Zend VM。这有利有弊。利------更好的资源控制:服务器和 PHP 代码的内存在单个 memory_limit 内统一核算。弊------Zend VM 存在一些性能问题,有时会影响服务器表现。
  • 服务器尽可能将数据结构直接解析为 PHP 数组。

单端口多协议

单个服务器支持多种协议。HTTP/1.1、HTTP/2、WebSocket、SSE 和 gRPC 共享同一个 TCP 端口和同一个事件循环;协议选择通过 ALPN(TLS 场景下)或 HTTP Upgrade 完成。HTTP/3 通过 QUIC 运行在同一 UDP 端口上,并通过 Alt-Svc 头部告知客户端,使客户端在后续请求中无缝切换。

这意味着一次 $server->start() 调用即可同时通过 HTTP/2 提供 REST API、通过 Server-Sent Events 推送事件、维持 WebSocket 连接,并暴露 gRPC 端点。

服务器优化策略

高吞吐量并非靠一个大招实现------而是许多小决策的总和:

  • 热路径上的池化。请求体缓冲区、压缩编码器、HTTP/3 流、连接槽位------一切都在池中管理。重复请求不会打扰分配器和内核;编码器被复用而非重新创建。
  • 大缓冲区的几何增长 。PHP 标准的 smart_str 存在一个隐藏的性能悬崖:超过某个阈值后,每次增长都会变成一次系统调用,其开销随缓冲区大小而增长。在大请求体上,这曾消耗多达一半的请求时间。
  • 热路径上的零拷贝 。multipart 解析器直接操作传入缓冲区。HTTP/2 无需中间 PHP 缓冲区即可提供静态内容,HTTP/1 对大文件回退到 sendfile()
  • 与内核网络栈友好协作SO_REUSEPORT 将连接分散到工作进程,将头部与响应体合并发送,chunk 大小匹配 TLS 记录大小。
  • 并发请求间的共享内存。一个请求打开的文件,其缓冲区可被另一个请求复用。

这些优化使代码相对于实际工作负载保持轻量。服务器嵌入到 TrueAsync 事件循环中,使其在协程之间工作:当 PHP 代码等待数据库响应时,服务器接受下一个请求。当协程进入 I/O 等待时,反应器立即处理下一个就绪事件------没有线程空闲等待。

API 概览

服务器的公开 API 包含两个基本类:

  • HttpServerConfig------配置对象。
  • HttpServer------服务器本身,由配置创建并启动。

一个最小应用示例:

php 复制代码
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;

$server = new HttpServer(
    (new HttpServerConfig())->addListener('0.0.0.0', 8080)
);

$server->addHttpHandler(function ($request, $response) {
    $response->setStatusCode(200)->setBody('Hello, World!');
});

$server->start();   // 阻塞当前线程直到 stop() 被调用

监听器

监听器是"协议 + 传输层 + 主机 + 端口"的组合。

  • addListener()------TCP,HTTP/1.1 + HTTP/2(通过首字节或 ALPN 选择);
  • addHttp1Listener() / addHttp2Listener()------限制为单一协议的端口;
  • addHttp3Listener()------UDP/QUIC;
  • addUnixListener()------Unix 域套接字。

处理器

处理器是在每个新请求到达时被调用的函数。它们接收请求和响应对象,并可以像往常一样操作它们。服务器支持多种类型的处理器,每种对应特定的协议:

php 复制代码
$server->addHttpHandler(fn ($req, $res) => /* ... */);   // HTTP/1.1 + HTTP/2
$server->addHttp2Handler(fn ($req, $res) => /* ... */);  // HTTP/2 专用
$server->addWebSocketHandler(fn ($req, $res) => /* ... */);

每个处理器在自己的协程中运行:HTTP/1------每个请求一个协程,HTTP/2 和 HTTP/3------每个流一个协程。

当处理器进入 await(例如等待数据库响应)时,它既不会阻塞其他连接,也不会阻塞其他流。

请求与响应

请求对象是只读的。其 API 包括:getMethod()getUri()getHttpVersion()getHeader() / getHeaderLine() / getHeaders() / hasHeader()(头部名称不区分大小写)、getContentType()getContentLength()getBody()。对于表单和文件上传------getPost()getFiles()getFile()

响应对象是唯一的输出通道。设置器支持链式调用(流式接口):

php 复制代码
$response
    ->setStatusCode(200)
    ->setHeader('Content-Type', 'text/plain')
    ->setBody('payload')
    ->end();

流式传输

由于服务器支持 HTTP/2 和 HTTP/3,它从底层就为流式传输而构建。开发者可以完全控制数据何时发送到客户端,而无需等待处理器完成。服务器不强制在内存中持有整个响应体才能一次性发送。响应有两种模式:

  • 缓冲模式 ------setBody() / write() 累积响应体,在结束时一次性通过网络发出;
  • 流式模式 ------send() 将下一个数据块直接推送到网络。
php 复制代码
$server->addHttpHandler(function ($req, $res) {
    $res->setStatusCode(200)->setHeader('Content-Type', 'text/event-stream');

    foreach (fetch_events() as $event) {   // 数据源可能是无限的
        $res->send("data: {$event}\n\n");  // 数据块立即发出
    }
    $res->end();                           // 关闭流
});

第一次调用 send() 会提交头部;此后,对于 HTTP/1.1 这是 Transfer-Encoding: chunked,对于 HTTP/2 和 HTTP/3 则是独立的 DATA 帧。换句话说,同一段处理器代码可以在任何协议下生成正确的流------服务器负责处理协议差异。

流式传输对 HTTP/2 尤其有用。HTTP/2 中每个请求是同一连接内的独立流,每个流在自己的协程中运行。流式输出意味着可以随着数据生成即时发送------Server-Sent Events、导出大型报告、gRPC 流式传输------而无需将全部数据展开到内存中。由于 HTTP/2 具有每个流的流量控制窗口,慢速客户端不应该撑爆服务器内存。为此提供了 $res->sendable() 方法------它报告流是否准备好立即接收下一个数据块;如果未就绪,协程只需让出给其他协程,直到窗口释放。

请求体也可以作为流到达。使用 Request::readBody() 方法可以分块读取请求体,而无需等待完整接收:

php 复制代码
while (($chunk = $req->readBody()) !== null) {
    $sink->write($chunk);   // 每次处理 64 KiB,而不是一次性处理 2 GiB
}

这样一来,GB 级别的上传可以被代理到文件或另一个服务,而不必在内存中组装完整数据。

文件处理器

服务器实现了一个专门优化的静态文件服务路径。

它是一个构建器类 StaticHandler,在服务器上与常规处理器一起注册:

php 复制代码
use TrueAsync\StaticHandler;

$static = (new StaticHandler('/assets/', '/var/www/public'))
    ->setIndexFiles('index.html')
    ->enablePrecompressed('br', 'gzip', 'zstd')
    ->setCacheControl('public, max-age=86400');

$server->addStaticHandler($static);

第一个参数是 URL 前缀(虚拟挂载路径),第二个是磁盘上的目录。处理器的主要目标是以最快速度提供文件服务,而不切换到 PHP 代码。

以下特性已开箱即用:

  • MIME 类型 ------内置 44 种扩展名的表格(二分查找),外加通过 setMimeType() 自定义覆盖。
  • 条件请求 ------基于 (mtime, size, inode) 的弱 ETag,处理 If-None-Match / If-Modified-Since 并返回 304 响应,支持 HEAD 请求。
  • 范围请求 ------206 Partial Content、Content-Range,对无效范围的正确 416 响应。
  • 预压缩文件 ------如果 main.css.br / .gz / .zst 与文件同目录且客户端接受,服务器直接提供现成的压缩文件,而非即时压缩。
  • 安全性 ------防止路径穿越(../%2e%2e、NUL 字节、反斜杠),dotfile 策略(.git/ 默认关闭),符号链接策略(拒绝 / 跟随 / OwnerMatch),按 glob 掩码隐藏文件。
  • 打开文件缓存 ------可选的按处理器配置的打开文件缓存,支持 LRU 和 TTL:在热点文件集上跳过 stat、ETag 计算和 MIME 查找,在基准测试中额外带来约 20% 的性能提升。

虽然看起来这种处理器对 API 应用没什么用,但它在某些场景下可能派上用场,比如为通过 URL 前缀隐藏的私密文件提供服务。例如,如果动态生成报告并保存到受保护目录,可以通过 StaticHandler 提供服务,无需打开 PHP 文件,也无需将内容读入内存。

未来展望

虽然项目仍处于早期阶段,但已经可以试用。WebSocket、gRPC 和遥测等后续功能将在未来几个月中逐步推出。 TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器

相关推荐
北冥you鱼2 分钟前
Go 语言读取链上数据:从基础到实战
开发语言·后端·golang
程序员cxuan20 分钟前
Claude Code 为了封禁中国用户,竟然在代码里下毒
人工智能·后端·程序员
郡杰26 分钟前
Git基础与开发平台搭建
git·后端
ServBay42 分钟前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好1 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780512 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python
Java面试题总结2 小时前
CubeSandbox 实战:从零部署到快照/克隆/回滚全体验
后端
Csvn2 小时前
Python 开发技巧:函数式编程与 lambda 进阶
后端
考虑考虑2 小时前
nginx配置ssl
运维·后端·nginx
uhakadotcom2 小时前
Python 中,`None` 不等于 `False`导致的问题
后端·面试·github