在高性能服务器开发领域,每一毫秒的延迟优化和每一次系统调用的减少都至关重要。如今,随着 Swoole 6.2 正式发布,一个里程碑式的突破随之而来:该版本全面引入了 io_uring 技术,用以替代传统的 epoll 实现异步 IO。
实测数据给出了令人印象深刻的答案:
- Swoole + io_uring 的 QPS 高达 146,872!
- 性能是 Golang HTTP Server 的 3.06 倍
- 性能是 Node.js 的 4.44 倍
- 平均延迟从 2.81ms 大幅降至 1.36ms,性能提升超过 100%
这不仅仅是一次常规的性能优化,更是 PHP 在高并发服务领域一次具有颠覆意义的进化。
📊 测试环境与对比基准
为确保测试的公平性与可比性,所有服务均运行在同一台物理机上,并且限制为单核 CPU 执行,以避免多线程调度带来的干扰。
| 项目 |
配置 |
| CPU |
Intel® Core™ i7-8700K @ 3.70GHz × 12 核 |
| 内存 |
32GB DDR4 |
| 系统 |
Ubuntu 22.04.5 LTS |
| 工具 |
wrk -c 200 -d 5s 模拟高并发 HTTP 请求 |
测试说明
- ✅ Golang net/http(设置
GOMAXPROCS=1)
- ✅ Node.js http 模块
- ✅ Swoole 6.2 Coroutine Http Server(两种模式):
- 使用传统
epoll
- 启用
io_uring 新架构(uring-socket)
Golang 测试代码
package main
import (
"fmt"
"log"
"net/http"
"runtime"
)
func main() {
runtime.GOMAXPROCS(1)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Server", "golang-http-server")
fmt.Fprint(w, "<h1>\nHello world!\n</h1>\n")
})
log.Printf("Go http Server listen on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Node.js 测试代码
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {
'Server': "node.js"
});
res.end("<h1>Hello World</h2>");
}).listen(8080, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8080/');
Swoole 测试代码
<?php
$pool = new Swoole\Process\Pool(1, SWOOLE_IPC_NONE);
$pool->on('WorkerStart', function($pool, $workerId){
Swoole\Runtime::setHookFlags(0);
Co\run(function(){
$server = new Swoole\Coroutine\Http\Server("127.0.0.1", 9501, false, true);
$server->handle('/', function($request, $response){
$response->end("<h1>\nHello world!\n</h1>\n");
});
$server->start();
});
});
$pool->start();
测试结果
Golang
wrk -c 200 -d 5s http://127.0.0.1:8080/
Running 5s test @ http://127.0.0.1:8080/
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.26ms 2.40ms 46.67ms 83.66%
Req/Sec 24.19k 3.43k 27.42k 81.00%
240611 requests in 5.01s, 58.74MB read
Requests/sec: 48008.89
Transfer/sec: 11.72MB
Node.js
wrk -c 200 -d 5s http://127.0.0.1:8080/
Running 5s test @ http://127.0.0.1:8080/
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 13.21ms 46.34ms 617.05ms 96.85%
Req/Sec 16.68k 1.59k 17.56k 96.00%
165991 requests in 5.01s, 28.34MB read
Requests/sec: 33114.29
Transfer/sec: 5.65MB
Swoole 6.2 (uring-socket)
wrk -c 200 -d 5s http://127.0.0.1:9501/
Running 5s test @ http://127.0.0.1:9501/
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.36ms 210.22us 2.65ms 96.28%
Req/Sec 73.91k 7.25k 77.98k 92.00%
734585 requests in 5.00s, 124.00MB read
Requests/sec: 146872.82
Transfer/sec: 24.79MB
Swoole 6.2 (epoll)
wrk -c 200 -d 5s http://127.0.0.1:9501/
Running 5s test @ http://127.0.0.1:9501/
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.81ms 623.43us 18.10ms 87.26%
Req/Sec 35.89k 3.96k 39.48k 85.00%
357169 requests in 5.01s, 60.29MB read
Requests/sec: 71252.96
Transfer/sec: 12.03MB
📈 性能对比结果一览
| 框架/语言 |
Requests/sec (QPS) |
Transfer/sec |
平均延迟 |
| Golang |
48,009 |
11.72 MB |
4.26ms |
| Node.js |
33,114 |
5.65 MB |
13.21ms |
| Swoole (epoll) |
71,253 |
12.03 MB |
2.81ms |
| Swoole (io_uring) |
146,873 |
24.79 MB |
1.36ms |
🔺 Swoole + io_uring 在单线程下的并发能力就达到了约 15 万 QPS,相比自身的 epoll 模式提升超过 100%,相比 Golang 提升近 3 倍,相比 Node.js 提升近 4.4 倍,性能表现显著优于主流语言运行时!
🧠 为什么 io_uring 如此强大?技术原理解密
要理解这次性能飞跃,我们需要深入到 Linux 内核层面,探究 io_uring 究竟带来了哪些革命性的变革。
1️⃣ 传统 IO 模型的瓶颈:epoll 的天花板
长期以来,Linux 下的高性能网络服务高度依赖 epoll 与非阻塞 socket 相结合的事件驱动模型。其基本工作流程可以概括为:
用户态程序 -> epoll_ctl 添加监听 -> 发生事件 -> epoll_wait 返回 -> read/write 系统调用 -> 数据拷贝 -> 处理请求
但这个经典模型存在几个固有的性能瓶颈:
- ❌ 频繁系统调用开销大:每一个
accept、read、write 操作都需要进行一次用户态到内核态的系统调用。
- ❌ 上下文切换成本高:频繁的用户态与内核态之间切换会消耗大量 CPU 资源。
- ❌ 无法批量处理 IO 请求:每次只能提交一个 IO 操作,无法聚合。
- ❌ 数据拷贝次数多:尤其在未启用零拷贝优化时,数据在内核与用户缓冲区之间的来回复制成为性能杀手。
当并发连接数达到数万级别时,这些看似微小的开销叠加起来,就会形成一个巨大的性能黑洞。
2️⃣ io_uring:Linux 5.1 引入的异步 IO 革命
io_uring 是由 Linux 块设备层核心维护者 Jens Axboe 于 2019 年引入 Linux 5.1 的全新异步 IO 接口。它的设计目标是 彻底重构用户空间与内核之间的 IO 通信机制。
✅ 核心优势一:共享内存 Ring Buffer 设计
io_uring 的核心在于使用了两个环形缓冲区:提交队列(Submission Queue, SQ)和完成队列(Completion Queue, CQ)。这两个队列通过 mmap 直接映射到用户进程的地址空间,实现了用户态与内核态之间的无锁并发访问。
+------------------+ +--------------------+
| 用户程序 |<--->| 内核 |
| - 提交 IO 请求 | | - 执行 IO 操作 |
| - 轮询完成队列 | | - 写入完成事件 |
+------------------+ +--------------------+
↑_________________________↓
共享内存,无需系统调用!
这种设计带来了根本性的改变:
- ✔️ 提交 IO 请求时,用户程序只需向共享内存中的 SQ 写入数据,无需执行系统调用。
- ✔️ 获取 IO 完成事件时,可以通过轮询 CQ 或配置中断来实现,方式灵活。
- ✔️ 几乎完全消除了上下文切换的开销。
✅ 核心优势二:批量提交 & 批量完成(Batching)
传统的 epoll 模型下,一次 epoll_wait 调用最多返回几百个就绪事件,并且每个具体的 IO 操作(如 read/write)都需要单独发起一次系统调用。
而 io_uring 原生支持批量操作:
// 一次性提交多个 IO 请求
io_uring_submit_multiple(&ring, sqes, count);
// 一次性获取多个完成事件
io_uring_peek_batch_cqe(&ring, &cqes, max);
在 Swoole 这样的网络服务器场景中,可以将成百上千个 recv、send、accept 等 socket 操作打包,一次性提交给内核处理。这极大地降低了单位请求的平均处理开销,在处理海量短连接时优势尤为明显。
✅ 核心优势三:零拷贝支持(Zero-Copy I/O)
这是实现性能飞跃的关键特性之一。
借助 io_uring 提供的 IORING_SETUP_SQPOLL 和 MSG_ZEROCOPY 等特性,Swoole 可以实现:
- 数据直接从内核的页缓存(page cache)发送到网卡,完全不经过用户态的缓冲区。
- 发送 HTTP 响应时,可以使用
splice() 或 send_zc() 等系统调用,避免内存复制。
- 这种特性特别适合静态文件服务、高频 API 响应等场景。
例如,在返回一个简单的 “Hello World” 响应时,数据可以直接从内核映射到 socket 的发送缓冲区,跳过了从用户态内存拷贝到内核态的多余步骤。
✅ 核心优势四:内核线程轮询模式(SQ Polling)
当启用 IORING_SETUP_SQPOLL 标志后,内核会启动一个专用的内核线程来持续轮询(poll)提交队列(SQ)。这意味着 用户进程完全无需主动触发任何系统调用,内核线程就能自动发现并执行新的 IO 请求。
这种模式对追求极致低延迟和高吞吐的服务极为友好。它特别适合 Swoole 协程调度器这种会产生高频、细粒度 IO 操作的场景,使得 IO 提交的延迟降至最低。
🔄 Swoole 如何集成 io_uring?
Swoole 6.2 在底层对协程调度器与 Socket 层进行了重构,新增了 uring-socket 模块,实现了对 io_uring 接口的完整封装。在编译安装 Swoole 扩展时,需要添加 --enable-uring-socket 以及 --enable-iouring 参数来启用此功能。
💡 注意:
- 需要 Linux 5.5(且内核配置
CONFIG_IO_URING=y)以上版本的内核,推荐使用 Ubuntu 22.04 或更新的操作系统。
- 若在 Docker 环境中使用
io_uring,需要在运行容器时添加 --security-opt seccomp=unconfined 参数以放宽安全限制。
🤔 为什么 PHP+Swoole 能超越 Golang 和 Node.js?
许多人可能会感到惊讶:“不是常说 Go 和 JavaScript 的运行时更适合高并发吗?” 其实,答案就藏在底层 IO 模型的选择上。
| 对比项 |
Golang |
Node.js |
Swoole (io_uring) |
| IO 模型 |
epoll + goroutine |
epoll + event loop |
io_uring + 协程 |
| 系统调用 |
多次 syscall |
多次 syscall |
极少甚至无 syscall |
| 数据拷贝 |
通常有 copy |
有 copy |
支持零拷贝 |
| 批处理能力 |
弱 |
弱 |
强(批量提交) |
| 上下文切换 |
中等 |
中等 |
极低(共享内存) |
👉 Swoole + io_uring 所做的并非简单的“优化”,而是一次彻底的“换道超车”!
它不再受限于传统的“系统调用 + 事件回调”编程范式,而是迈入了“用户态与内核态通过共享内存协同计算”的新时代。这种底层架构的先进性,直接转化为了压倒性的性能优势。
🛠️ 如何体验 Swoole 6.2 + io_uring?
步骤 1:确认系统支持
uname -r
# 内核版本必须 >= 5.5 (建议 5.10+)
# 检查内核是否编译了 io_uring 支持
grep CONFIG_IO_URING /boot/config-$(uname -r)
# 输出应为:CONFIG_IO_URING=y
步骤 2:编译安装 Swoole 6.2+
假设已下载 Swoole 源码并进入其目录:
phpize
./configure --enable-uring-socket --enable-iouring
make -j $(nproc)
sudo make install
最后,别忘了在 php.ini 中添加 extension=swoole。
步骤 3:编写测试代码并启用协程
<?php
Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL);
Co\run(function(){
$server = new Swoole\Coroutine\Http\Server("127.0.0.1", 9501, false, true);
$server->handle('/', function($req, $resp){
$resp->end("<h1>Hello World</h1>");
});
$server->start();
});
✅ 运行上述脚本,然后访问 http://127.0.0.1:9501,你就站在了一个能轻松支撑百万级 QPS 的服务起点上!
🎯 展望未来:PHP 的高性能时代已来
过去,“PHP 是世界上最好的语言”更多是一种社区内的调侃。但现在,随着 Swoole 的持续进化、PHP 8.x 系列 JIT 编译器的引入,以及 io_uring 等底层技术的加持,PHP 正在蜕变为一个真正意义上的高性能服务端语言。
不妨想象一下这些场景:
- 微服务网关用 PHP 来编写?
- 实时聊天服务器基于 Swoole 构建?
- 百万并发量的 API 网关运行在 PHP 上?
这些曾经看似遥远的构想,如今已触手可及。Swoole 6.2 的发布,标志着 PHP 在云原生与高性能计算时代正式宣告回归。
📣 结语:重估 PHP 的价值,把握 io_uring 的浪潮
“性能不是一切,但没有性能,什么都不是。”
Swoole 团队通过这次硬核的技术升级证明了一个事实:只要勇于突破底层系统的限制,深入整合最前沿的内核特性,PHP 同样可以在高性能的战场上,与 Golang、Node.js 等选手正面较量并取得优势。
如果你仍在沿用传统的 PHP-FPM 配合 Nginx 的架构运行 Laravel 等应用,或许是时候审视并升级你的技术栈了。
🔥 拥抱协程,拥抱真正的异步,拥抱 io_uring —— 下一代基于 PHP 的高性能微服务与网络应用,就此启程。
📚 参考资料
本文旨在探讨技术可能性,更多关于高性能 网络编程 与后端架构的深度讨论,欢迎访问 云栈社区 与广大开发者交流。