Swoole之所以能支撑高并发,靠的是Master、Manager、Worker组成的多进程多线程架构。但很多开发者把配置文件抄过来就直接上线,worker_num还是默认值,reactor_num从来没动过,结果要么CPU跑不满,要么进程数开太多内存被吃光。进程和线程的数量配置直接决定了服务器的吞吐能力和资源占用,这篇文章就来把Swoole进程管理的这套机制讲透,并给出可落地的调优方法。

Swoole多进程模型的构成
Swoole启动一个Server后,内存中至少存在三类进程。第一个是Master主进程,它负责创建reactor线程组,处理网络的监听和事件循环,所有客户端连接的读写都由reactor线程完成。第二个是Manager管理进程,它不处理任何业务逻辑,只负责fork和回收Worker子进程,当某个Worker因为致命错误退出时,Manager会立即拉起新的进程顶上,保证服务不中断。
第三个是Worker进程,这才是执行PHP回调代码的地方。所有的onReceive、onRequest、onConnect事件最终都分发到Worker中执行。如果开启了任务投递功能,还会有TaskWorker进程,专门处理耗时的异步任务,避免阻塞正常请求的Worker。另外某些模式下还存在UserWorker自定义进程,用于跑定时器、监控脚本之类的常驻逻辑。
理解了这个结构,就能明白参数调整的思路:reactor线程管的是网络IO,Worker进程管的是业务计算,TaskWorker管的是耗时任务。三者的数量配置逻辑完全不同,不能一概而论。
reactor_num与CPU核心数的关系
reactor_num控制Master进程中reactor线程的数量,默认值是CPU核数,官方建议不要超过CPU核数的四倍。reactor线程做的事是纯IO事件分发,几乎不消耗CPU,所以一般情况下保持默认即可,不需要手动调大。
有一种情况值得调整:如果你的服务器上还跑着其他服务,Swoole只分到了部分核心,那么可以通过taskset或者cgroup把Swoole绑核,再把reactor_num设置成绑定的核数,避免reactor线程被内核调度到已经被占用的核心上,造成上下文切换的开销。
$server = new Swoole\Server('0.0.0.0', 9501);
$server->set([
'reactor_num' => 4, // reactor线程数,默认为CPU核数
'worker_num' => 8, // worker进程数
]);
$server->on('Receive', function ($server, $fd, $reactorId, $data) {
$server->send($fd, "hello\n");
});
$server->start();
需要特别注意的是,reactor线程数并不是越大越好。线程过多时,内核在多个线程之间做事件分发的锁竞争反而会加剧,吞吐量可能不升反降。压测时可以对比2核、4核、8核配置下的QPS曲线,通常会发现默认配置已经接近最优。
worker_num的设置方法与常见误区
worker_num是最核心的参数,它决定了同一时刻能并行处理多少个请求。默认值是CPU核数,这个默认值适用于业务逻辑偏计算密集的场景。但如果你的业务里有数据库查询、Redis调用、HTTP外呼这类IO等待操作,Worker大部分时间都在阻塞等待,适当调大worker_num能明显提升并发能力,官方甚至允许设置为CPU核数的四倍或更高。
判断方法是看CPU利用率:如果压测时CPU只用了百分之二三十,但请求堆积严重、响应时间变长,说明Worker都在等IO,此时应该增加worker_num;反过来如果CPU已经跑满,再加Worker只会让进程互相争抢时间片,此时应该做的是优化业务代码或者加机器,而不是盲目调大进程数。
一个常见的误区是一次性把worker_num开到几十上百。每个Worker都是一个完整的PHP进程,基础内存占用通常在二十到三十兆以上,加上业务代码和框架的加载,单进程占用上百兆也不少见。进程开太多不仅浪费内存,还会让进程间通信的开销显著上升,甚至触发系统的进程数限制。合理的做法是从CPU核数的一到两倍开始,通过压测逐步上调,同时观察内存占用和QPS变化。
另外,Worker内部默认是同步阻塞模式,一个Worker同一时刻只能处理一个请求。如果使用了协程,协程模式下一个Worker可以并发处理大量连接,此时worker_num反而不需要太大,设置为CPU核数的一半到一倍通常就够了,这也是很多老项目升级协程后最容易被忽略的调整点。
task_worker_num与max_request的配合
task_worker_num设置TaskWorker进程的数量,用于处理通过task()方法投递的异步任务,比如发邮件、写日志、生成报表这类耗时操作。TaskWorker的阻塞不会影响主服务的响应速度,所以它的数量估算比较直接:用每秒投递的任务数乘以单个任务的平均耗时,得到同时需要运行的进程数,再留出百分之三十的余量。例如每秒投递一百个任务,每个任务耗时两百毫秒,那么至少需要二十个TaskWorker才能消费得过来。
max_request则是一个容易被忽视的守护参数。它指定每个Worker处理多少次请求后自动退出重启,由Manager拉起全新进程,这样可以主动释放进程运行期间累积的内存,防止PHP的内存缓慢泄漏把服务器拖垮。经验值一般设在几千到几万之间,业务内存增长快的项目取小值。如果TaskWorker的任务比较重,也应该单独设置max_request,避免长期运行的任务进程内存膨胀。
$server->set([
'worker_num' => 8,
'task_worker_num' => 20, // 异步任务进程数
'max_request' => 5000, // worker处理5000个请求后重启
'max_request_grace' => 100, // 宽限值,平滑重启
'dispatch_mode' => 2, // 固定分配,保证请求顺序
]);
配置完成后,可以通过ps命令查看进程树,确认Worker和TaskWorker的数量是否与配置一致,也可以调用stats()方法拿到各进程的实时状态。线上建议接入进程监控,观察每个Worker的处理量和内存曲线,用真实数据代替拍脑袋,才能找到属于你自己业务的最优数量组合。
Swoole进程管理Swoole线程数配置Swoole性能优化修改时间:2026-09-05 23:58:48