Swoole 的七年:我在 PHP 高并发上的探索
在全面转向 Go 之前,我在 PHP 高并发上探索了七年,主要武器就是 Swoole。它让 PHP 从"每请求一进程"进化到"常驻内存 + 协程",把 PHP 的并发能力从几百 QPS 拉到几千。这篇文章是七年的完整复盘——包括为什么最终迁移到了 Go。
一、Swoole 解决了什么
传统 PHP-FPM 的瓶颈:每请求启动一个进程,初始化框架、连数据库、加载代码,全浪费在重复劳动上。
Swoole 的核心改变:
再加 协程:一个进程内可以并发处理大量请求(IO 等待时让出 CPU)。
二、Swoole 的核心能力
1. 常驻内存(省掉重复初始化)
|
|
收益:单机吞吐从 FPM 的 500 QPS → Swoole 的 3000+ QPS(压测数据)。
2. 协程(IO 密集场景的王牌)
|
|
理解:PHP-FPM 下 100 个并发 = 100 个进程;Swoole 协程下 100 个并发 = 1 个进程内 100 个协程,内存占用降一个数量级。
3. 连接池
|
|
注意:协程环境下连接不能共享(一个协程在等结果时,另一个协程用了同一连接会乱)——协程连接池要按协程隔离,Swoole 的 ConnectionPool 会自动处理。
三、Swoole 的坑(血泪史)
坑 1:全局变量跨请求泄漏
|
|
解法:请求级数据放协程上下文(Swoole\Coroutine::getContext()),禁止裸全局变量。
坑 2:静态变量缓存污染
|
|
解法:缓存 key 必须含请求维度(userId),或用协程上下文。
坑 3:IO 阻塞把协程卡死
|
|
铁律: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 的团队
- 如果 QPS < 2000:Swoole 完全够用,别折腾迁移
- 如果已到 5000+ 且持续增长:开始规划 Go 迁移(我写过《从 PHP 到 Go:转型路线图》)
- 迁移策略:新服务用 Go,存量高并发服务逐个迁,PHP 只读兜底(读写分离过渡)
总结
Swoole 七年教会我的三件事:
- 常驻内存 + 协程是 PHP 高并发的正确方向——Swoole 方向没错
- 全局变量、静态缓存、阻塞 IO是常驻内存的三座大山,每个都踩过
- 工具会过时,认知会留下:并发模型、连接池、内存管理这些认知,迁移到 Go 全部复用
Swoole 让我在 PHP 里理解了"什么是并发"——然后我发现,原生支持并发的语言,才是终点。