架构机制

代码加载与依赖注入

集群模式的启动逻辑

首先是 egg-scripts/start.js 中封装启动参数,并且 Fork 新的进程来启动:
1
// 封装参数
2
this.serverBin = path.join(__dirname, '../start-cluster');
3
...
4
const eggArgs = [
5
...(execArgv || []),
6
this.serverBin,
7
clusterOptions,
8
`--title=${argv.title}`
9
];
10
...
11
// 启动执行进程
12
spawn('node', eggArgs, options);
Copied!
然后 start-cluster 中加载 egg-cluster 的启动代码:
1
// start-cluster
2
require(options.framework).startCluster(options);
Copied!
egg-cluster/index.js 中暴露了 startCluster 方法,其会构造 Master 对象实例:
1
exports.startCluster = function(options, callback) {
2
new Master(options).ready(callback);
3
};
Copied!
Master 本身继承自 EventEmitter,并且包含了 Manager 与 Messenger 这两个消息组件的实例以及 AgentWorker 与 AppWorker 两类具体的工作进程。messenger.js 则是负责在 Master、Agent 与 App Worker 之间的通信:
1
┌────────┐
2
│ parent │
3
/└────────┘\
4
/ | \
5
/ ┌────────┐ \
6
/ │ master │ \
7
/ └────────┘ \
8
/ / \ \
9
┌───────┐ ┌───────┐
10
│ agent │ ------- │ app │
11
└───────┘ └───────┘
Copied!
在 Master 的构造函数中,其会使用 detectPort 检测端口是否可用;若可用则调用 forkAgentWorker 创建 AgentWorker:
1
// AgentWorker 创建完毕后创建 AppWorkers
2
this.once('agent-start', this.forkAppWorkers.bind(this));
3
4
// 使用 fork 创建新的 AgentWorker
5
const agentWorker = childprocess.fork(agentWorkerFile, args, opt);
Copied!
在 forkAppWorkers 中调用 cfork 来创建多个 AppWorker,并且将它们注册到管理中心:
1
cfork({
2
exec: appWorkerFile,
3
args,
4
silent: false,
5
count: this.options.workers,
6
// don't refork in local env
7
refork: this.isProduction
8
});
9
10
cluster.on('fork', worker => {
11
...
12
this.workerManager.setWorker(worker);
13
...
14
});
15
16
// 子进程开始监听后,发送 app-start 消息
17
cluster.on('listening', (worker, address) => {
18
this.messenger.send({
19
action: 'app-start',
20
...
21
});
22
});
Copied!
app-start 事件会触发 onAppStart,并且启动所有的 AppWorker:
1
// enable all workers when app started
2
for (const id in cluster.workers) {
3
const worker = cluster.workers[id];
4
worker.disableRefork = false;
5
}
Copied!
egg-cluster 允许传入 sticky 属性,来控制是否启动门面服务器;如果传入,则会调用 startMasterSocketServer 方法:
1
// manager.js
2
startMasterSocketServer(cb) {
3
// Create the outside facing server listening on our port.
4
require('net').createServer({ pauseOnConnect: true }, connection => {
5
if (!connection.remoteAddress) {
6
connection.close();
7
} else {
8
const worker = this.stickyWorker(connection.remoteAddress);
9
worker.send('sticky-session:connection', connection);
10
}
11
}).listen(this[REALPORT], cb);
12
}
Copied!
这里的 stickyWorker 会根据 IP 地址动态分配具体的 Worker 实例,每个 Worker 是完整的 Node.js 应用:
1
// app_worker.js
2
const Application = require(options.framework).Application;
3
const app = new Application(options);
Copied!
应用创建完毕后会调用 startServer 方法来启动本地服务器:
1
// app_worker.js
2
if (options.https) {
3
const httpsOptions = Object.assign({}, options.https, {
4
key: fs.readFileSync(options.https.key),
5
cert: fs.readFileSync(options.https.cert)
6
});
7
server = require('https').createServer(httpsOptions, app.callback());
8
} else {
9
server = require('http').createServer(app.callback());
10
}
Copied!
如果是 Sticky 模式,则监听 127.0.0.1,否则共享连接:
1
// app_worker.js
2
if (options.sticky) {
3
server.listen(0, '127.0.0.1');
4
} else {
5
server.listen(...args);
6
}
Copied!
Last modified 2yr ago