Skip to content

Commit e814ca4

Browse files
committed
feat: allow worker listen different port to support nginx sticky
1 parent d424239 commit e814ca4

File tree

15 files changed

+119
-39
lines changed

15 files changed

+119
-39
lines changed

lib/app_worker.js

+35-5
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ const fs = require('fs');
1313
const debug = require('debug')('egg-cluster');
1414
const gracefulExit = require('graceful-process');
1515
const ConsoleLogger = require('egg-logger').EggConsoleLogger;
16+
const net = require('net');
1617
const consoleLogger = new ConsoleLogger({ level: process.env.EGG_APP_WORKER_LOGGER_LEVEL });
1718
const Application = require(options.framework).Application;
1819
debug('new Application with options %j', options);
1920
const app = new Application(options);
20-
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
21-
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
22-
const port = options.port = options.port || listenConfig.port;
23-
process.send({ to: 'master', action: 'realport', data: port });
2421
app.ready(startServer);
2522

2623
function exitProcess() {
@@ -31,6 +28,7 @@ function exitProcess() {
3128

3229
// exit if worker start timeout
3330
app.once('startTimeout', startTimeoutHandler);
31+
3432
function startTimeoutHandler() {
3533
consoleLogger.error('[app_worker] start timeout, exiting with code:1');
3634
exitProcess();
@@ -43,6 +41,10 @@ function startServer(err) {
4341
exitProcess();
4442
return;
4543
}
44+
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
45+
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
46+
const port = options.port = options.port || listenConfig.port;
47+
process.send({ to: 'master', action: 'realport', data: port });
4648

4749
app.removeListener('startTimeout', startTimeoutHandler);
4850

@@ -66,7 +68,9 @@ function startServer(err) {
6668
app.emit('server', server);
6769

6870
if (options.sticky) {
69-
server.listen(options.stickyWorkerPort, '127.0.0.1');
71+
const args = [ options.stickyWorkerPort ];
72+
if (listenConfig.hostname) args.push(listenConfig.hostname);
73+
server.listen(...args);
7074
// Listen to messages sent from the master. Ignore everything else.
7175
process.on('message', (message, connection) => {
7276
if (message !== 'sticky-session:connection') {
@@ -93,6 +97,32 @@ function startServer(err) {
9397
server.listen(...args);
9498
}
9599
}
100+
server.on('listening', () => {
101+
// same as cluster.listening event
102+
// https://github.com/nodejs/node/blob/master/lib/internal/cluster/child.js#L76-L104
103+
// const address = server.address();
104+
let addressType = 4;
105+
let fd = null;
106+
let address = null;
107+
if (listenConfig.path) {
108+
addressType = -1;
109+
fd = server.address().fd;
110+
address = listenConfig.path;
111+
} else if (listenConfig.hostname) {
112+
const isIp = net.isIP(listenConfig.hostname);
113+
addressType = isIp === 0 ? 4 : isIp;
114+
address = listenConfig.hostname;
115+
}
116+
app.messenger.send('app-start', {
117+
workerPid: process.pid,
118+
address: {
119+
addressType,
120+
address,
121+
port,
122+
fd,
123+
},
124+
}, 'master');
125+
});
96126
}
97127

98128
gracefulExit({

lib/master.js

+17-28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const terminate = require('./utils/terminate');
2323

2424
const APP_ADDRESS = Symbol('Master#appAddress');
2525
const REALPORT = Symbol('Master#realport');
26+
const WORKER_ADDRESSES = Symbol('Master#workerAddresses');
2627

2728

2829
class Master extends EventEmitter {
@@ -38,6 +39,7 @@ class Master extends EventEmitter {
3839
* - {Object} [https] https options, { key, cert }, full path
3940
* - {Array|String} [require] will inject into worker/agent process
4041
* - {String} [pidFile] will save master pid to this file
42+
* - {boolean} [nginxSticky] - use nginx sticky mode, default false
4143
*/
4244
constructor(options) {
4345
super();
@@ -59,6 +61,7 @@ class Master extends EventEmitter {
5961
if (process.env.EGG_SERVER_ENV === 'local' || process.env.NODE_ENV === 'development') {
6062
this.logMethod = 'debug';
6163
}
64+
this[WORKER_ADDRESSES] = [];
6265

6366
// get the real framework info
6467
const frameworkPath = this.options.framework;
@@ -83,9 +86,14 @@ class Master extends EventEmitter {
8386

8487
this.ready(() => {
8588
this.isStarted = true;
86-
const stickyMsg = this.options.sticky ? ' with STICKY MODE!' : '';
87-
this.logger.info('[master] %s started on %s (%sms)%s',
88-
frameworkPkg.name, this[APP_ADDRESS], Date.now() - startTime, stickyMsg);
89+
if (this.options.nginxSticky) {
90+
this.logger.info('[master] %s started on %j (%sms) with NGINX STICKY MODE!',
91+
frameworkPkg.name, this[WORKER_ADDRESSES].map(getAddress), Date.now() - startTime);
92+
} else {
93+
const stickyMsg = this.options.sticky ? ' with STICKY MODE!' : '';
94+
this.logger.info('[master] %s started on %s (%sms)%s',
95+
frameworkPkg.name, this[APP_ADDRESS], Date.now() - startTime, stickyMsg);
96+
}
8997

9098
const action = 'egg-ready';
9199
this.messenger.send({ action, to: 'parent', data: { port: this[REALPORT], address: this[APP_ADDRESS] } });
@@ -299,14 +307,6 @@ class Master extends EventEmitter {
299307
from: 'app',
300308
});
301309
});
302-
cluster.on('listening', (worker, address) => {
303-
this.messenger.send({
304-
action: 'app-start',
305-
data: { workerPid: worker.process.pid, address },
306-
to: 'master',
307-
from: 'app',
308-
});
309-
});
310310
}
311311

312312
/**
@@ -460,15 +460,12 @@ class Master extends EventEmitter {
460460
const worker = this.workerManager.getWorker(data.workerPid);
461461
const address = data.address;
462462

463-
// worker should listen stickyWorkerPort when sticky mode
464-
if (this.options.sticky) {
465-
if (String(address.port) !== String(this.options.stickyWorkerPort)) {
466-
return;
467-
}
468-
// worker should listen REALPORT when not sticky mode
469-
} else if (!isUnixSock(address)
470-
&& (String(address.port) !== String(this[REALPORT]))) {
471-
return;
463+
address.protocal = this.options.https ? 'https' : 'http';
464+
if (this.options.nginxSticky) {
465+
this[WORKER_ADDRESSES].push(address);
466+
} else {
467+
address.port = this.options.sticky ? this[REALPORT] : address.port;
468+
this[APP_ADDRESS] = getAddress(address);
472469
}
473470

474471
// send message to agent with alive workers
@@ -506,10 +503,6 @@ class Master extends EventEmitter {
506503
worker.disableRefork = false;
507504
}
508505

509-
address.protocal = this.options.https ? 'https' : 'http';
510-
address.port = this.options.sticky ? this[REALPORT] : address.port;
511-
this[APP_ADDRESS] = getAddress(address);
512-
513506
if (this.options.sticky) {
514507
this.startMasterSocketServer(err => {
515508
if (err) return this.ready(err);
@@ -623,7 +616,3 @@ function getAddress({ addressType, address, port, protocal }) {
623616
const hostname = address || '127.0.0.1';
624617
return `${protocal}://${hostname}:${port}`;
625618
}
626-
627-
function isUnixSock(address) {
628-
return address.addressType === -1;
629-
}

test/fixtures/apps/app-listen-hostname/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module.exports = app => {
44
// don't use the port that egg-mock defined
5-
app._options.port = undefined;
5+
app.options.port = undefined;
66
};

test/fixtures/apps/app-listen-path/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module.exports = app => {
44
// don't use the port that egg-mock defined
5-
app._options.port = undefined;
5+
app.options.port = undefined;
66
};

test/fixtures/apps/app-listen-port/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module.exports = app => {
44
// don't use the port that egg-mock defined
5-
app._options.port = undefined;
5+
app.options.port = undefined;
66
};

test/fixtures/apps/app-listen-port/app/router.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ module.exports = app => {
66
});
77

88
app.get('/port', function* () {
9-
this.body = this.app._options.port;
9+
this.body = this.app.options.port;
1010
});
1111
};

test/fixtures/apps/app-listen-without-port/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module.exports = app => {
44
// don't use the port that egg-mock defined
5-
app._options.port = undefined;
5+
app.options.port = undefined;
66
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
module.exports = app => {
4+
// don't use the port that egg-mock defined
5+
app.options.port = undefined;
6+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
exports.index = function* () {
4+
this.body = 'hi cluster';
5+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
module.exports = function(app) {
4+
// GET / 302 to /portal/i.htm
5+
app.redirect('/', '/portal/i.htm', 302);
6+
7+
// GET /portal/i.htm => controllers/home.js
8+
app.get('/portal/i.htm', app.controller.home.index);
9+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
const { worker } = require('cluster');
4+
5+
module.exports = {
6+
keys: '123',
7+
cluster: {
8+
listen: {
9+
port: worker ? 17010 + worker.id : 17010,
10+
},
11+
},
12+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "cluster_mod_nginx_sticky"
3+
}

test/fixtures/apps/cluster_mod_sticky/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const net = require('net');
44

55
module.exports = app => {
66
// don't use the port that egg-mock defined
7-
app._options.port = undefined;
7+
app.options.port = undefined;
88
const server = net.createServer();
99
server.listen(9500);
1010

test/index.test.js

Whitespace-only changes.

test/master.test.js

+26
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,32 @@ describe('test/master.test.js', () => {
837837
});
838838
});
839839

840+
describe('--nginx-sticky', () => {
841+
before(() => {
842+
app = utils.cluster('apps/cluster_mod_nginx_sticky', {
843+
nginxSticky: true,
844+
workers: 2,
845+
});
846+
app.debug();
847+
return app.ready();
848+
});
849+
850+
after(() => app.close());
851+
852+
it('should listen multi port', async () => {
853+
app.expect('stdout', /\[master] egg started on \["http:\/\/127\.0\.0\.1:17011|2","http:\/\/127\.0\.0\.1:17012|1"]/);
854+
const urls = [
855+
'http://127.0.0.1:17011',
856+
'http://127.0.0.1:17012',
857+
];
858+
await Promise.all(urls.map(t =>
859+
request(t).get('/portal/i.htm')
860+
.expect('hi cluster')
861+
.expect(200)
862+
));
863+
});
864+
});
865+
840866
describe('agent and worker exception', () => {
841867
it('should not exit when local env', function* () {
842868
mm.env('local');

0 commit comments

Comments
 (0)