利用 Swoole 协程特性
默认配置 task_worker_num => 4 在面对批量推送消息时严重不足。当需要给成百上千个用户推送消息时,循环调用 Swoole 的 push 或投递大量小任务,会瞬间挤爆这 4 个进程,导致后续任务全部排队甚至超时。
请按照以下分级方案进行优化,方案一和方案二可以立即见效:
方案一:立即调整配置(治标)
在批量推送场景下,进程主要在等待网络 I/O 响应,属于 I/O 密集型 任务。你可以大胆地将进程数放大。
修改 config/swoole.php 中的 options:
php
'options' => [
// 1. 大幅提升进程数:建议调整为 16 或 32(甚至 64,取决于你的用户量和服务器内存)
'task_worker_num' => 32,
// 2. 必须调大任务队列缓冲区(防止任务因为堆积而直接丢弃或报错)
'task_max_request' => 2000,
'socket_buffer_size' => 128 * 1024 * 1024, // 128M
],
请谨慎使用此类代码。
方案二:改变投递策略(治本)
绝对不要在循环中为每个用户单独投递一个 Task(例如:循环 1000 次投递 1000 个 Task)。这会产生高额的进程间通信(IPC)开销。
正确做法:打包成一个或数个大任务投递。
错误写法:
php
// 导致 task 瞬间不够用的元凶 foreach ($userIds as $id) { $server->task(['type' => 'push', 'uid' => $id, 'msg' => $data]); }请谨慎使用此类代码。
正确写法(分批打包):
php
// 将 10000 个用户切分成每 500 个一组,只投递 20 个 Task $chunks = array_chunk($userIds, 500); foreach ($chunks as $chunkUids) { $server->task(['type' => 'batch_push', 'uids' => $chunkUids, 'msg' => $data]); }请谨慎使用此类代码。
在 Task 消费端处理:
php
// 在 onTask 事件中拿到一组 uids 循环 push foreach ($data['uids'] as $uid) { if ($server->isEstablished($fd)) { // 确保连接有效 $server->push($fd, $data['msg']); } }请谨慎使用此类代码。
方案三:利用 Swoole 协程特性(高性能选型)
推送消息本身是非常适合协程的。如果开启了协程,你甚至可以不用 Task 进程,直接在 Worker 中利用协程并发去推送,速度会比 Task 快数倍。
确保
config/swoole.php开启了'coroutine' => true。使用
Swoole\Coroutine\Barrier或通道(Channel)并发推送:
php
use Swoole\Coroutine;
use Swoole\Coroutine\Barrier;
// 在 WebSocket 路由或事件中直接执行
Coroutine::create(function() use ($userIds, $data, $server) {
$barrier = Barrier::create();
$chunks = array_chunk($userIds, 100); // 100个一组并发
foreach ($chunks as $chunk) {
// 自动并发,利用底层协程调度,不阻塞当前进程
Coroutine::create(function() use ($barrier, $chunk, $data, $server) {
foreach ($chunk as $uid) {
$fd = getFdByUid($uid); // 自定义获取 fd 的方法
if ($server->isEstablished($fd)) {
$server->push($fd, $data);
}
}
});
}
// 等待这一批全部送达
Barrier::wait($barrier);
});
许可协议:
CC BY 4.0