要让Swoole服务实现高QPS、低延迟、稳定内存的生产级表现,其实并不需要太多花哨的技巧——只需紧盯协程有效性、I/O阻塞点、上下文污染和连接池复用这四个关键环节,即可取得显著效果。首先验证协程和Hook是否同时启用,然后在WorkerStart中全量Hook确保协程内执行;将PDO、HTTP、Redis替换为协程版本;重置超全局变量、手动管理Session、注入协程专属实例;最后合理配置连接池以及worker_num、reactor_num、max_coroutine等参数。下面我们逐步展开详细解析。

许多开发者一开始就调整worker_num,认为增加worker数量就能应对高并发。然而,方向往往不对——Swoole的性能瓶颈通常不在于进程数,而在于协程是否真正生效、I/O是否存在阻塞、上下文是否被污染。以下四大要点,缺一不可。
确认协程是否真正启用
第一步非常直接:运行命令 php --ri swoole | grep coroutine,输出结果中必须同时包含 coroutine => enabled 和 hook => enabled,缺少任何一个都不行。如果只开启协程而不开启Hook,那么PDO、Redis、file_get_contents等调用仍然会同步阻塞,协程的优势无法充分发挥。
第二步:在server.php的WorkerStart回调中,添加 Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL)。需要特别注意的是,不能仅使用 SWOOLE_HOOK_PDO——因为PDO Hook仅对新创建的连接有效,如果框架在启动时已经提前创建了PDO实例,那么该Hook将毫无作用。
第三步:在任意控制器中调用 var_dump(Swoole\Coroutine::getPcid()),如果返回非null值,则表示当前确实处于协程环境;如果返回null,则说明请求并未进入协程调度器——此时应重点排查前两步。
替换所有阻塞I/O调用
数据库访问不要再使用 Db::table()->select() 等同步链式调用。应改用协程版本:
go(function() {
$mysql = new Swoole\Coroutine\MySQL();
$mysql->connect([]);
$mysql->query('SELECT * FROM user');
});
HTTP请求同样如此,file_get_contents 和 GuzzleHttp 的默认同步模式都应禁用。应使用 Swoole\Coroutine\Http\Client,并且务必设置超时——$client->set(['timeout' => 3]),否则协程可能无限挂起,导致整个worker进程崩溃。
Redis操作必须切换到 Swoole\Coroutine\Redis 驱动。这里有一个关键点:原生Redis扩展即使开启了Hook,也无法被协程化。Swoole的Redis协程客户端是独立实现的,不依赖PHP原生扩展,因此必须显式替换。
强制隔离协程上下文
这是最容易踩坑的地方,尤其是从传统FPM架构迁移过来的项目。以下四个动作必须做到位:
① 在onRequest回调的开头,立即重置超全局变量:$_GET = $request->get ?: []; $_POST = $request->post ?: []; $_SERVER = $request->server;。如果不重置,上一个请求残留的数据会污染当前请求。
② 禁用自动Session启动:ini_set('session.auto_start', 0),改为手动控制——session_id($request->cookie('PHPSESSID') ?: uniqid()); session_start();。这样每个请求的Session才能独立管理。
③ 每次响应结束前,必须调用 session_write_close(),否则后续协程会因文件锁而阻塞。这是ThinkPHP在Swoole环境下Session失效的最常见原因,没有之一。
④ 所有静态属性和单例对象(例如 Container::getInstance()->get('request'))都必须通过 Swoole\Coroutine\Context::set() 注入协程专属实例。如果多个请求共享同一个内存地址,数据污染将不可避免。
连接池与核心参数优化
Redis连接池的初始化,可以参考 src/Server/SwooleDistributedServer.php 第150行:$redis_pool = new RedisAsynPool($this->config, $this->config->get('redis.active'));。关键参数包括 max_connection(默认100000)、max_idle_time 等,需要根据业务峰值动态调整,而不是随意设定一个固定值。
worker_num 的经验值建议设置为CPU核心数的1.2至1.5倍。例如,8核CPU设为12较为合理。但需注意,该值必须通过压力测试验证——盲目设为16,反而会因上下文切换开销激增而导致性能下降。reactor_num 应等于CPU物理核心数,以避免单reactor过载引发accept延迟飙升。
max_coroutine 必须显式配置,否则默认值太小(通常仅3000),超出后会触发 fatal error:“coroutine stack size exhausted”。建议按公式 max_coroutine = (内存总量GB × 1024) ÷ 2 进行粗略估算,再结合压测结果微调。
