定位
Swoole 不是框架,是 PHP 扩展。 它给 PHP 加上协程、常驻内存、异步 IO 的能力,但不提供路由、ORM、模板。
装好 Swoole 之后,你依然需要框架——Hyperf 之所以能跑协程,是因为 Swoole 提供了底层运行时。
把这点搞清楚,后面所有讨论才成立。
安装
# 方式一:pecl
pecl install swoole
1. 方式二:源码编译(需要定制编译选项时)
wget https://github.com/swoole/swoole-src/archive/refs/tags/v6.2.4.tar.gz
tar -xzf v6.2.4.tar.gz && cd swoole-src-6.2.4
1. 注意:--enable-openssl 已在 6.2 移除,SSL 支持默认启用,不再需要该选项
phpize && ./configure --enable-http2 --enable-swoole-curl
make -j && sudo make install
1. 想启用 io_uring:加 --enable-iouring --enable-uring_socket
php.ini 里启用:
extension=swoole.so
验证:
php -m | grep swoole
php --ri swoole # 看编译选项
核心概念
1. 协程:用同步写法做异步
<?php
// 传统阻塞写法,三个请求串行,总耗时 = 1 + 2 + 3 = 6 秒
$a = file_get_contents('https://api.a.com'); // 阻塞
$b = file_get_contents('https://api.b.com');
// 协程写法,用 Swoole 的协程客户端
use Swoole\Coroutine;
use function Swoole\Coroutine\run;
run(function () {
$results = [];
// 并发发起三个请求,总耗时约等于最慢的那个
Coroutine\parallel(3, function ($i) use (&$results) {
$cli = new Coroutine\Http\Client('api.example.com', 443, true);
$cli->get('/');
$results[$i] = $cli->body;
});
});
关键:协程不是并行计算,是「IO 时让出 CPU」。所以它只对 IO 密集场景有效(网络请求、数据库、文件)。
2. 必须用协程化的客户端
这是最重要的一条。 普通 PHP 函数是阻塞的,在协程里调用会堵死整个进程:
| 用途 | 阻塞(不能用) | 协程化(要用) |
|---|---|---|
| HTTP | curl、file_get_contents | Swoole\Coroutine\Http\Client |
| MySQL | PDO、mysqli | Swoole\Coroutine\MySQL |
| Redis | redis 扩展 | Swoole\Coroutine\Redis |
| 文件 | file_get_contents | Swoole\Coroutine\System::readFile |
一个 file_get_contents 就能让整个服务的并发能力掉到个位数,而且不报错。
Swoole 提供了一键协程化的方案:
Server 场景下的一键协程化由启动选项控制——enable_coroutine 默认已开,钩子范围用按位或的组合常量指定(swoole.enable_library=php_stream 这类 ini 写法并不控制它):
$server = new Swoole\Http\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
// SWOOLE_HOOK_ALL = SWOOLE_HOOK_TCP | SWOOLE_HOOK_UDP | SWOOLE_HOOK_UNIX | SWOOLE_HOOK_UDG
// | SWOOLE_HOOK_SSL | SWOOLE_HOOK_TLS | SWOOLE_HOOK_STREAM_FUNCTION | SWOOLE_HOOK_FILE
// | SWOOLE_HOOK_SLEEP | SWOOLE_HOOK_PROC_OPEN | SWOOLE_HOOK_CURL | SWOOLE_HOOK_NATIVE_CURL ...
$server->set([
'hook_flags' => SWOOLE_HOOK_ALL,
]);
开了钩子后,PDO、curl、file_get_contents 会被自动替换成协程版本。这是最容易落地的方案,但要注意兼容性。
3. 常驻内存:全局状态会残留
// 危险:这个静态数组会在所有请求间共享
class Cache
{
public static array $data = [];
}
在 FPM 里每次请求都是新进程,静态变量清空。在 Swoole 常驻进程里,它会一直累积。
解决办法:用 Swoole\Coroutine::getContext() 存请求级数据:
use Swoole\Coroutine;
// getContext() 返回协程容器对象,不能对函数返回值直接下标赋值——先接住再写
$context = Coroutine::getContext();
$context['user_id'] = $id; // 只属于当前协程
4. 两种运行模式
| 模式 | 用法 | 适合 |
|---|---|---|
| 协程容器 | Coroutine\run(fn() => ...) | 脚本、CLI、和现有 FPM 项目混用 |
| 常驻 Server | Swoole\Http\Server 等 | 完整的高性能服务 |
常见坑
① 编译选项不全
--enable-http2、--enable-swoole-curl 这些编译选项决定了能力(6.2 起 SSL 已默认启用,不再有 --enable-openssl)。用 pecl 默认编译可能缺功能,后期改要重新编译。
② 协程内不能用 sleep
sleep() 会阻塞整个进程。要用 Swoole\Coroutine::sleep()。
③ 超全局变量不可靠
$_GET、$_POST、$_SERVER 在协程间会串。必须用 Swoole 提供的请求对象。
④ 版本差异大
4.x 和 5.x 的协程实现有变化(5.x 后更稳定),6.x 又是另一个台阶:6.2.0 起协程 HTTP 服务端支持 io_uring 事件机制(编译时需 --enable-iouring 加 --enable-uring_socket),并新增了协程版 FTP / SSH 客户端,同时适配了 PHP 8.5。当前最新是 6.2.4,6.3 已有 RC。教程版本要先确认,尤其别照着 4.x / 5.x 的老文章抄协程写法。
什么时候用它
直接装 Swoole 的合理场景:
- 用 Hyperf(它依赖 Swoole)
- 需要写常驻进程但不想引入 Workerman
- 现有项目想给某几个接口加速(用协程容器包一层)
不合适:想「装个扩展就让网站变快」。网站变快靠的是减少数据库查询、加缓存,不是换运行时。
一个务实的路径
如果你的目标是「让 PHP 服务更快」,建议的顺序是:
- 先加 OPcache(几行配置,立竿见影)
- 再做对象缓存(Redis 缓存热点查询)
- 然后 profile,找到真正的瓶颈
- 最后才考虑协程(如果瓶颈确实在「每请求初始化」)
跳过前三步直接上 Swoole,通常得到的是「更快的错误」——因为业务逻辑和查询没优化,协程只是让同样的查询跑得更快一点。
延伸阅读
- Hyperf 协程框架入门——建在 Swoole 之上的主流框架
- Webman 框架入门——常驻内存但不用协程的对照方案