swoole: onWorkerStart 方法以及WorkerId的理解
public function onWorkerStart(SwooleServer $server, $workerId) { // 1. 初始化Laravel应用 if (function_exists('laravel_init')) { laravel_init(); } // 2. 只在Worker进程0执行监控任务(避免重复) if ($workerId === 0) { // 定时清理不活跃连接(60秒检测一次,10分钟无活动断开) Timer::tick(60000, function() use ($server) { $this->connectionManager->cleanupInactive(600, $server); \Log::debug("清理不活跃连接完成", [ 'active_connections' => count($this->connectionManager->getAll()) ]); }); // 性能监控(30秒记录一次) Timer::tick(30000, function() use ($server) { $stats = $server->stats(); \Log::info('服务器状态监控', [ 'connections' => $stats['connection_num'] ?? 0, 'workers' => $stats['worker_num'] ?? 0, 'memory_usage' => round(memory_get_usage()/1024/1024, 2).'MB', 'coroutines' => \Swoole\Coroutine::stats()['coroutine_num'] ?? 0 ]); // 自动告警(连接数超过80%阈值) $maxConnections = $this->config['options']['max_conn'] ?? 1000; if (($stats['connection_num'] ?? 0) > $maxConnections * 0.8) { \Log::warning("连接数超过80%阈值", [ 'current' => $stats['connection_num'], 'max' => $maxConnections ]); } }); } }
关键优化点说明
优化方向 | 具体实现 |
---|---|
资源隔离 | 通过 $workerId === 0 确保监控任务只在主Worker执行,避免重复日志 |
连接管理 | 定时清理逻辑增加日志记录,可观察清理效果 |
动态阈值告警 | 基于配置的 max_conn 自动计算80%阈值,更智能 |
全量监控指标 | 包含连接数、Worker数、内存、协程数等核心指标 |
日志分级 | 使用 debug 记录清理细节,info 记录状态,warning 记录告警 |
配套的 ConnectionManager
优化建议
// app/WebSocket/Connections/ConnectionManager.php public function cleanupInactive($timeout, $server) { $now = time(); $count = 0; foreach ($this->connections as $fd => $connection) { if ($now - $connection->getLastActiveTime() > $timeout) { $server->close($fd); unset($this->connections[$fd]); $count++; } } return $count; // 返回清理的连接数,便于监控 }
在 Swoole 的 onWorkerStart
回调中,$workerId
参数标识了当前 Worker 进程的类型和序号,其取值和对应场景如下:
1. WorkerId 取值范围及含义
值范围 | 进程类型 | 典型场景 |
---|---|---|
0 ~ (n-1) |
普通 Worker 进程 | 处理客户端连接和业务逻辑(worker_num 配置的数量) |
n ~ (n+m-1) |
Task Worker 进程 | 处理异步任务(task_worker_num 配置的数量,需启用 task 功能) |
-1 |
Manager 进程 | 仅在使用 --enable-process-control 编译时出现(一般不用处理) |
注:n = worker_num
, m = task_worker_num
2. 典型配置示例
假设 server.php
配置如下:
$server->set([
'worker_num' => 4, // 4个普通Worker
'task_worker_num' => 2 // 2个TaskWorker
]);
则 $workerId
分布为:
-
0~3
: 普通 Worker(处理 WebSocket 连接) -
4~5
: Task Worker(处理耗时任务) -
-1
: Manager 进程(如果有)
3. 为什么用 $workerId === 0
控制定时任务?
if ($workerId === 0) {
Timer::tick(30000, function() { /* 监控任务 */ });
}
原因 | 说明 |
---|---|
避免重复执行 | 所有 Worker 进程都会触发 onWorkerStart ,但监控只需运行一次 |
负载均衡安全 | Worker 0 通常是主进程,更适合执行管理类任务 |
防止竞态条件 | 多进程同时操作同一资源(如写日志)可能导致冲突 |
兼容 Task Worker | Task Worker 的 ID 从 worker_num 开始编号,天然不会进入此逻辑 |
4. 其他常见判断模式
(1) 区分普通 Worker 和 Task Worker
if ($workerId < $server->setting['worker_num']) {
// 普通 Worker 逻辑(处理连接)
} else {
// Task Worker 逻辑(处理异步任务)
}
(2) 指定特定 Worker 执行任务
// 让 Worker 1 专负责定时任务
if ($workerId === 1) {
Timer::tick(1000, function() { /* 专项任务 */ });
}
(3) 忽略 Manager 进程
if ($workerId >= 0) {
// 仅 Worker 和 Task Worker 会执行
}
5. 生产环境建议
-
动态获取进程数
$isMainWorker = ($workerId === 0); $isTaskWorker = ($workerId >= $server->setting['worker_num']);
-
进程分工示例
public function onWorkerStart($server, $workerId) { match(true) { $workerId === 0 => $this->initMonitor(), // Worker 0: 监控 $workerId < 3 => $this->initWebSocket(), // Worker 1-2: WS 连接 $isTaskWorker => $this->initTaskHandler(), // Task Worker: 异步任务 default => null }; }
-
调试技巧
在日志中添加进程标识:\Log::info("Current Worker", [ 'id' => $workerId, 'type' => $isTaskWorker ? 'Task' : 'Worker' ]);
通过合理利用 $workerId
,可以实现多进程间的职责划分,避免资源竞争,是 Swoole 编程的关键设计点之一。
起点在哪,或许选择不了。重要的是,你追求的终点在哪!