aetherframework-cluster 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +90 -0
- package/README.md +1049 -0
- package/index.js +288 -0
- package/package.json +41 -0
- package/src/core/ClusterManager.js +109 -0
- package/src/core/HealthMonitor.js +571 -0
- package/src/core/LoadBalancer.js +531 -0
- package/src/core/WorkerManager.js +619 -0
- package/src/examples/advanced-cluster.js +150 -0
- package/src/examples/basic-cluster.js +107 -0
- package/src/examples/benchmark-cluster.js +112 -0
- package/src/examples/simple-app.js +52 -0
- package/src/middleware/cluster-health.js +330 -0
- package/src/middleware/graceful-shutdown.js +443 -0
- package/src/middleware/process-monitor.js +925 -0
- package/src/middleware/worker-stats.js +879 -0
- package/src/utils/cpu-detector.js +78 -0
- package/src/utils/env-loader.js +140 -0
- package/src/utils/signal-handler.js +90 -0
package/index.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// packages/cluster/index.js
|
|
2
|
+
import ClusterManager from './src/core/ClusterManager.js';
|
|
3
|
+
import WorkerManager from './src/core/WorkerManager.js';
|
|
4
|
+
import HealthMonitor from './src/core/HealthMonitor.js';
|
|
5
|
+
import LoadBalancer from './src/core/LoadBalancer.js';
|
|
6
|
+
import { loadEnv } from './src/utils/env-loader.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AetherJS Cluster Module
|
|
10
|
+
* High-performance cluster management for Node.js applications
|
|
11
|
+
*/
|
|
12
|
+
class ClusterModule {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
// 加载环境配置
|
|
15
|
+
this.env = loadEnv();
|
|
16
|
+
|
|
17
|
+
// 合并配置
|
|
18
|
+
this.config = {
|
|
19
|
+
enabled: this.env.CLUSTER_ENABLED !== 'false',
|
|
20
|
+
workers: this.env.CLUSTER_WORKERS || 'auto',
|
|
21
|
+
port: parseInt(this.env.CLUSTER_PORT) || 3000,
|
|
22
|
+
gracefulShutdown: this.env.CLUSTER_GRACEFUL_SHUTDOWN !== 'false',
|
|
23
|
+
restartOnExit: this.env.CLUSTER_RESTART_ON_EXIT !== 'false',
|
|
24
|
+
restartDelay: parseInt(this.env.CLUSTER_RESTART_DELAY) || 1000,
|
|
25
|
+
healthCheck: {
|
|
26
|
+
enabled: this.env.CLUSTER_HEALTH_CHECK_ENABLED !== 'false',
|
|
27
|
+
interval: parseInt(this.env.CLUSTER_HEALTH_CHECK_INTERVAL) || 30000,
|
|
28
|
+
timeout: parseInt(this.env.CLUSTER_HEALTH_CHECK_TIMEOUT) || 5000
|
|
29
|
+
},
|
|
30
|
+
loadBalancing: {
|
|
31
|
+
enabled: this.env.CLUSTER_LOAD_BALANCING !== 'false',
|
|
32
|
+
type: this.env.CLUSTER_LOAD_BALANCER_TYPE || 'round-robin',
|
|
33
|
+
maxRequests: parseInt(this.env.CLUSTER_MAX_REQUESTS_PER_WORKER) || 1000
|
|
34
|
+
},
|
|
35
|
+
monitoring: {
|
|
36
|
+
enabled: this.env.CLUSTER_MONITORING_ENABLED !== 'false',
|
|
37
|
+
interval: parseInt(this.env.CLUSTER_MONITORING_INTERVAL) || 10000,
|
|
38
|
+
memoryThreshold: parseFloat(this.env.CLUSTER_MEMORY_THRESHOLD) || 0.8,
|
|
39
|
+
cpuThreshold: parseFloat(this.env.CLUSTER_CPU_THRESHOLD) || 0.7
|
|
40
|
+
},
|
|
41
|
+
...config
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.clusterManager = null;
|
|
45
|
+
this.workerManager = null;
|
|
46
|
+
this.healthMonitor = null;
|
|
47
|
+
this.loadBalancer = null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 初始化集群模块
|
|
52
|
+
* @param {Function} appFactory - 应用工厂函数
|
|
53
|
+
* @returns {Object|null} 应用实例或 null
|
|
54
|
+
*/
|
|
55
|
+
initialize(appFactory) {
|
|
56
|
+
if (!this.config.enabled) {
|
|
57
|
+
console.log('🚫 Cluster module is disabled, running in single process mode');
|
|
58
|
+
return appFactory();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('🚀 Initializing AetherJS Cluster Module...');
|
|
62
|
+
|
|
63
|
+
// 创建核心组件
|
|
64
|
+
this.clusterManager = new ClusterManager(this.config);
|
|
65
|
+
this.workerManager = new WorkerManager(this.config);
|
|
66
|
+
this.healthMonitor = new HealthMonitor(this.config.healthCheck);
|
|
67
|
+
this.loadBalancer = new LoadBalancer(this.config.loadBalancing);
|
|
68
|
+
|
|
69
|
+
// 启动集群
|
|
70
|
+
return this.clusterManager.start(() => {
|
|
71
|
+
// 工作进程中的逻辑
|
|
72
|
+
const app = appFactory();
|
|
73
|
+
|
|
74
|
+
// 设置工作进程应用
|
|
75
|
+
this.setupWorkerApp(app);
|
|
76
|
+
|
|
77
|
+
return app;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 设置工作进程应用
|
|
83
|
+
* @param {Object} app - 应用实例
|
|
84
|
+
*/
|
|
85
|
+
setupWorkerApp(app) {
|
|
86
|
+
// 添加集群中间件
|
|
87
|
+
if (this.healthMonitor) {
|
|
88
|
+
app.use(this.healthMonitor.middleware());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 添加集群路由
|
|
92
|
+
this.setupClusterRoutes(app);
|
|
93
|
+
|
|
94
|
+
// 设置进程间通信
|
|
95
|
+
this.setupIPC();
|
|
96
|
+
|
|
97
|
+
// 启动监控
|
|
98
|
+
this.startMonitoring();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 设置集群路由
|
|
103
|
+
* @param {Object} app - 应用实例
|
|
104
|
+
*/
|
|
105
|
+
setupClusterRoutes(app) {
|
|
106
|
+
// 健康检查端点
|
|
107
|
+
app.get('/cluster/health', async (ctx) => {
|
|
108
|
+
const health = await this.healthMonitor.getHealthStatus();
|
|
109
|
+
ctx.body = health;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 集群统计端点
|
|
113
|
+
app.get('/cluster/stats', (ctx) => {
|
|
114
|
+
const stats = this.workerManager.getStats();
|
|
115
|
+
ctx.body = {
|
|
116
|
+
status: 'ok',
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
stats: {
|
|
119
|
+
...stats,
|
|
120
|
+
pid: process.pid,
|
|
121
|
+
isWorker: !process.env.isPrimary,
|
|
122
|
+
workerId: process.env.workerId,
|
|
123
|
+
uptime: process.uptime(),
|
|
124
|
+
memory: process.memoryUsage(),
|
|
125
|
+
cpu: process.cpuUsage()
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 工作进程管理端点
|
|
131
|
+
app.post('/cluster/workers/:pid/restart', (ctx) => {
|
|
132
|
+
const { pid } = ctx.params;
|
|
133
|
+
const success = this.clusterManager.restartWorker(parseInt(pid));
|
|
134
|
+
|
|
135
|
+
ctx.body = {
|
|
136
|
+
status: success ? 'ok' : 'error',
|
|
137
|
+
message: success ? `Worker ${pid} restart initiated` : `Worker ${pid} not found`,
|
|
138
|
+
pid
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 负载均衡信息
|
|
143
|
+
app.get('/cluster/load-balancer', (ctx) => {
|
|
144
|
+
const info = this.loadBalancer.getInfo();
|
|
145
|
+
ctx.body = info;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 设置进程间通信
|
|
151
|
+
*/
|
|
152
|
+
setupIPC() {
|
|
153
|
+
process.on('message', (message) => {
|
|
154
|
+
switch (message.type) {
|
|
155
|
+
case 'SHUTDOWN':
|
|
156
|
+
this.handleShutdown(message);
|
|
157
|
+
break;
|
|
158
|
+
case 'RESTART':
|
|
159
|
+
this.handleRestart(message);
|
|
160
|
+
break;
|
|
161
|
+
case 'STATS_REQUEST':
|
|
162
|
+
this.handleStatsRequest(message);
|
|
163
|
+
break;
|
|
164
|
+
case 'HEALTH_CHECK':
|
|
165
|
+
this.handleHealthCheck(message);
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
console.log(`Received unknown message type: ${message.type}`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 处理关闭信号
|
|
175
|
+
* @param {Object} message - IPC 消息
|
|
176
|
+
*/
|
|
177
|
+
handleShutdown(message) {
|
|
178
|
+
console.log(`Worker ${process.pid} received shutdown signal`);
|
|
179
|
+
|
|
180
|
+
if (this.config.gracefulShutdown) {
|
|
181
|
+
// 执行优雅关闭
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}, this.config.shutdownTimeout || 5000);
|
|
185
|
+
} else {
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 处理重启信号
|
|
192
|
+
* @param {Object} message - IPC 消息
|
|
193
|
+
*/
|
|
194
|
+
handleRestart(message) {
|
|
195
|
+
console.log(`Worker ${process.pid} received restart signal`);
|
|
196
|
+
process.exit(1); // 非0退出码会触发重启
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 处理统计请求
|
|
201
|
+
* @param {Object} message - IPC 消息
|
|
202
|
+
*/
|
|
203
|
+
handleStatsRequest(message) {
|
|
204
|
+
const stats = {
|
|
205
|
+
pid: process.pid,
|
|
206
|
+
memory: process.memoryUsage(),
|
|
207
|
+
cpu: process.cpuUsage(),
|
|
208
|
+
uptime: process.uptime(),
|
|
209
|
+
requests: this.workerManager ? this.workerManager.getRequestCount() : 0
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
process.send({
|
|
213
|
+
type: 'STATS_RESPONSE',
|
|
214
|
+
data: stats
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 处理健康检查
|
|
220
|
+
* @param {Object} message - IPC 消息
|
|
221
|
+
*/
|
|
222
|
+
handleHealthCheck(message) {
|
|
223
|
+
const health = {
|
|
224
|
+
pid: process.pid,
|
|
225
|
+
status: 'healthy',
|
|
226
|
+
timestamp: new Date().toISOString()
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
process.send({
|
|
230
|
+
type: 'HEALTH_RESPONSE',
|
|
231
|
+
data: health
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 启动监控
|
|
237
|
+
*/
|
|
238
|
+
startMonitoring() {
|
|
239
|
+
if (this.config.monitoring.enabled) {
|
|
240
|
+
setInterval(() => {
|
|
241
|
+
this.healthMonitor.check();
|
|
242
|
+
}, this.config.monitoring.interval);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 获取集群管理器
|
|
248
|
+
* @returns {ClusterManager} 集群管理器实例
|
|
249
|
+
*/
|
|
250
|
+
getClusterManager() {
|
|
251
|
+
return this.clusterManager;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 获取工作进程管理器
|
|
256
|
+
* @returns {WorkerManager} 工作进程管理器实例
|
|
257
|
+
*/
|
|
258
|
+
getWorkerManager() {
|
|
259
|
+
return this.workerManager;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 获取健康监控器
|
|
264
|
+
* @returns {HealthMonitor} 健康监控器实例
|
|
265
|
+
*/
|
|
266
|
+
getHealthMonitor() {
|
|
267
|
+
return this.healthMonitor;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 获取负载均衡器
|
|
272
|
+
* @returns {LoadBalancer} 负载均衡器实例
|
|
273
|
+
*/
|
|
274
|
+
getLoadBalancer() {
|
|
275
|
+
return this.loadBalancer;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 获取配置
|
|
280
|
+
* @returns {Object} 配置对象
|
|
281
|
+
*/
|
|
282
|
+
getConfig() {
|
|
283
|
+
return this.config;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 导出模块
|
|
288
|
+
export default ClusterModule;
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aetherframework-cluster",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Aether Framework - Cluster Management Module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"coverage": "jest --coverage"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"aether",
|
|
15
|
+
"framework",
|
|
16
|
+
"cluster",
|
|
17
|
+
"load-balancing",
|
|
18
|
+
"scalability"
|
|
19
|
+
],
|
|
20
|
+
"author": "Aether Framework Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"os": "^0.1.2"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@aetherframework/core": "^1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=14.0.0"
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/aetherjs/aetherframework-cluster.git",
|
|
35
|
+
"directory": "packages/cluster"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/aetherjs/aetherframework-cluster/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/aetherframework/aether-api#readme"
|
|
41
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// package/cluster/core/ClusterManager.js
|
|
2
|
+
import cluster from 'cluster';
|
|
3
|
+
import EventEmitter from 'events';
|
|
4
|
+
|
|
5
|
+
export default class ClusterManager extends EventEmitter {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
// 从环境变量读取配置,提供默认值
|
|
10
|
+
const defaultWorkers = process.env.CLUSTER_WORKERS === 'auto' ?
|
|
11
|
+
os.cpus().length - 1 : // 自动检测CPU核心数,保留1个给系统
|
|
12
|
+
parseInt(process.env.CLUSTER_WORKERS) || 7;
|
|
13
|
+
|
|
14
|
+
const defaultPort = parseInt(process.env.CLUSTER_PORT) || 3000;
|
|
15
|
+
const defaultGracefulShutdown = process.env.CLUSTER_GRACEFUL_SHUTDOWN !== 'false';
|
|
16
|
+
const defaultRestartOnExit = process.env.CLUSTER_RESTART_ON_EXIT !== 'false';
|
|
17
|
+
const defaultWorkerReusePort = process.env.CLUSTER_WORKER_REUSE_PORT === 'true';
|
|
18
|
+
const defaultEnableIPC = process.env.CLUSTER_ENABLE_IPC !== 'false';
|
|
19
|
+
const defaultIpcTimeout = parseInt(process.env.CLUSTER_IPC_TIMEOUT) || 5000;
|
|
20
|
+
const defaultShutdownTimeout = parseInt(process.env.CLUSTER_SHUTDOWN_TIMEOUT) || 10000;
|
|
21
|
+
|
|
22
|
+
this.options = {
|
|
23
|
+
workers: defaultWorkers,
|
|
24
|
+
port: defaultPort,
|
|
25
|
+
gracefulShutdown: defaultGracefulShutdown,
|
|
26
|
+
restartOnExit: defaultRestartOnExit,
|
|
27
|
+
workerReusePort: defaultWorkerReusePort,
|
|
28
|
+
enableIPC: defaultEnableIPC,
|
|
29
|
+
ipcTimeout: defaultIpcTimeout,
|
|
30
|
+
shutdownTimeout: defaultShutdownTimeout,
|
|
31
|
+
...options
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.readyCount = 0;
|
|
35
|
+
this.isShuttingDown = false;
|
|
36
|
+
|
|
37
|
+
if (cluster.isPrimary) this.setupMaster();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setupMaster() {
|
|
41
|
+
console.log(`🚀 Master process ${process.pid} is running`);
|
|
42
|
+
console.log(`📊 Configuration: workers=${this.options.workers}, port=${this.options.port}`);
|
|
43
|
+
console.log(`⚙️ Settings: gracefulShutdown=${this.options.gracefulShutdown}, restartOnExit=${this.options.restartOnExit}`);
|
|
44
|
+
|
|
45
|
+
cluster.on('fork', (worker) => {
|
|
46
|
+
console.log(`🔄 Worker ${worker.id} (PID: ${worker.process.pid}) forked`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
cluster.on('online', (worker) => {
|
|
50
|
+
console.log(`✅ Worker ${worker.process.pid} is online`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
cluster.on('message', (worker, message) => {
|
|
54
|
+
if (message?.type === 'READY') {
|
|
55
|
+
this.readyCount++;
|
|
56
|
+
console.log(`🚀 Worker ${message.pid} is ready (${this.readyCount}/${this.options.workers})`);
|
|
57
|
+
this.emit('worker:ready', message);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
cluster.on('exit', (worker, code) => {
|
|
62
|
+
console.log(`❌ Worker ${worker.process.pid} exited (${code})`);
|
|
63
|
+
if (!this.isShuttingDown && this.options.restartOnExit) {
|
|
64
|
+
console.log(`🔄 Restarting worker ${worker.process.pid}`);
|
|
65
|
+
cluster.fork({ PORT: this.options.port.toString() });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
start() {
|
|
71
|
+
if (!cluster.isPrimary) return;
|
|
72
|
+
|
|
73
|
+
console.log(`📊 Starting ${this.options.workers} workers on port ${this.options.port}`);
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < this.options.workers; i++) {
|
|
76
|
+
cluster.fork({
|
|
77
|
+
PORT: this.options.port.toString(),
|
|
78
|
+
WORKER_ID: i + 1,
|
|
79
|
+
IS_WORKER: 'true'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
stop() {
|
|
87
|
+
if (!cluster.isPrimary) return;
|
|
88
|
+
|
|
89
|
+
this.isShuttingDown = true;
|
|
90
|
+
console.log('⚠️ Received shutdown signal, initiating shutdown...');
|
|
91
|
+
|
|
92
|
+
const workers = Object.values(cluster.workers || {});
|
|
93
|
+
console.log(`📤 Sending shutdown signal to ${workers.length} workers`);
|
|
94
|
+
|
|
95
|
+
workers.forEach(worker => {
|
|
96
|
+
if (worker.isConnected()) {
|
|
97
|
+
worker.send({ type: 'SHUTDOWN' });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
console.log('✅ All workers shut down successfully');
|
|
104
|
+
console.log('👋 Master process exiting');
|
|
105
|
+
resolve();
|
|
106
|
+
}, this.options.shutdownTimeout);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|