Swoole 的七年:我在 PHP 高并发上的探索

在全面转向 Go 之前,我在 PHP 高并发上探索了七年,主要武器就是 Swoole。它让 PHP 从"每请求一进程"进化到"常驻内存 + 协程",把 PHP 的并发能力从几百 QPS 拉到几千。这篇文章是七年的完整复盘——包括为什么最终迁移到了 Go。

一、Swoole 解决了什么

传统 PHP-FPM 的瓶颈:每请求启动一个进程,初始化框架、连数据库、加载代码,全浪费在重复劳动上。

Swoole 的核心改变:

P S H w P o - o F l P e M

再加 协程:一个进程内可以并发处理大量请求(IO 等待时让出 CPU)。

二、Swoole 的核心能力

1. 常驻内存(省掉重复初始化)

1
2
3
4
// 一次加载,所有请求复用
$pdo = new PDO(...);          // 连接复用
$redis = new Redis(...);      // 连接池化
$compiled = require 'config.php';  // 配置只加载一次

收益:单机吞吐从 FPM 的 500 QPS → Swoole 的 3000+ QPS(压测数据)。

2. 协程(IO 密集场景的王牌)

1
2
3
4
5
6
// Swoole 协程:IO 等待自动让出,不阻塞进程
go(function () use ($http) {
    $res = co::curl('http://internal-api/resume');  // 协程版 curl
    // 等待期间,进程去处理其他请求
    echo $res;
});

理解:PHP-FPM 下 100 个并发 = 100 个进程;Swoole 协程下 100 个并发 = 1 个进程内 100 个协程,内存占用降一个数量级

3. 连接池

1
2
3
4
// Swoole 连接池:预建连接复用,避免每次握手
$pool = new ConnectionPool(function () {
    return new PDO($dsn);
}, 50);  // 池大小 50

注意:协程环境下连接不能共享(一个协程在等结果时,另一个协程用了同一连接会乱)——协程连接池要按协程隔离,Swoole 的 ConnectionPool 会自动处理。

三、Swoole 的坑(血泪史)

坑 1:全局变量跨请求泄漏

1
2
3
// FPM 下安全:每次请求独立进程
$user = getUser();      // 全局变量
// Swoole 下灾难:常驻内存,下个请求还能读到上一个的 $user

解法:请求级数据放协程上下文(Swoole\Coroutine::getContext()),禁止裸全局变量。

坑 2:静态变量缓存污染

1
2
3
// 静态缓存:第一次请求写入,第二个用户读到第一个用户的数据
static $cache = [];
$cache[$userId] = getUser($userId);   // 别人的 userId 也在这

解法:缓存 key 必须含请求维度(userId),或用协程上下文。

坑 3:IO 阻塞把协程卡死

1
2
3
// 协程里用了同步阻塞函数 → 整个进程卡住(不是只卡一个协程)
file_get_contents('https://api.example.com');   // ❌ 阻塞
Swoole\Coroutine\Http\Client::get('api.example.com');  // ✅ 协程版

铁律:Swoole 环境下所有 IO 必须用协程版本(协程 MySQL/Redis/Http/文件),任何同步阻塞都会拖垮整个进程。

坑 4:内存泄漏(常驻内存的代价)

FPM 每请求结束自动回收;Swoole 常驻进程里泄漏的变量永远不会释放

解法:监控内存(memory_get_usage() + 定期巡检),定位泄漏(慢查询、大数组滞留)。

四、为什么最终迁移到 Go

诚实说,Swoole 七年下来,我觉得它完成了历史使命,但天花板明显

维度 Swoole Go
协程 需手动写协程版 API 语言原生(goroutine)
类型安全 动态类型,重构靠自觉 静态类型 + 编译期检查
生态 扩展质量参差,调试难 标准库 + 工具链成熟
并发模型 协程 + 手动管理 goroutine + channel
部署 需 Care 常驻进程 静态二进制
招聘/维护 会的人少 团队共识

结论:Swoole 是"PHP 的补丁",Go 是"为并发而生的语言"。当业务真正进入高并发深水区,换语言比打补丁划算

五、给还在用 PHP + Swoole 的团队

  1. 如果 QPS < 2000:Swoole 完全够用,别折腾迁移
  2. 如果已到 5000+ 且持续增长:开始规划 Go 迁移(我写过《从 PHP 到 Go:转型路线图》)
  3. 迁移策略:新服务用 Go,存量高并发服务逐个迁,PHP 只读兜底(读写分离过渡)

总结

Swoole 七年教会我的三件事:

  1. 常驻内存 + 协程是 PHP 高并发的正确方向——Swoole 方向没错
  2. 全局变量、静态缓存、阻塞 IO是常驻内存的三座大山,每个都踩过
  3. 工具会过时,认知会留下:并发模型、连接池、内存管理这些认知,迁移到 Go 全部复用

Swoole 让我在 PHP 里理解了"什么是并发"——然后我发现,原生支持并发的语言,才是终点。