Skip to content

Commit 7127c43

Browse files
committed
feat: allow worker listen different port to support nginx sticky
1 parent d6b6102 commit 7127c43

File tree

15 files changed

+122
-58
lines changed

15 files changed

+122
-58
lines changed

lib/app_worker.js

+37-18
Original file line numberDiff line numberDiff line change
@@ -13,26 +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 consoleLogger = new ConsoleLogger({
17-
level: process.env.EGG_APP_WORKER_LOGGER_LEVEL,
18-
});
16+
const net = require('net');
17+
const consoleLogger = new ConsoleLogger({ level: process.env.EGG_APP_WORKER_LOGGER_LEVEL });
1918
const Application = require(options.framework).Application;
2019
debug('new Application with options %j', options);
2120
const app = new Application(options);
22-
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
23-
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
24-
const httpsOptions = Object.assign({}, clusterConfig.https, options.https);
25-
const port = options.port = options.port || listenConfig.port;
26-
const protocol = (httpsOptions.key && httpsOptions.cert) ? 'https' : 'http';
27-
28-
process.send({
29-
to: 'master',
30-
action: 'realport',
31-
data: {
32-
port,
33-
protocol,
34-
},
35-
});
3621

3722
app.ready(startServer);
3823

@@ -57,6 +42,12 @@ function startServer(err) {
5742
exitProcess();
5843
return;
5944
}
45+
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
46+
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
47+
const port = options.port = options.port || listenConfig.port;
48+
const httpsOptions = Object.assign({}, clusterConfig.https, options.https);
49+
const protocol = (httpsOptions.key && httpsOptions.cert) ? 'https' : 'http';
50+
process.send({ to: 'master', action: 'realport', data: { port, protocol } });
6051

6152
app.removeListener('startTimeout', startTimeoutHandler);
6253

@@ -81,7 +72,9 @@ function startServer(err) {
8172
app.emit('server', server);
8273

8374
if (options.sticky) {
84-
server.listen(options.stickyWorkerPort, '127.0.0.1');
75+
const args = [ options.stickyWorkerPort ];
76+
if (listenConfig.hostname) args.push(listenConfig.hostname);
77+
server.listen(...args);
8578
// Listen to messages sent from the master. Ignore everything else.
8679
process.on('message', (message, connection) => {
8780
if (message !== 'sticky-session:connection') {
@@ -108,6 +101,32 @@ function startServer(err) {
108101
server.listen(...args);
109102
}
110103
}
104+
server.on('listening', () => {
105+
// same as cluster.listening event
106+
// https://github.com/nodejs/node/blob/master/lib/internal/cluster/child.js#L76-L104
107+
// const address = server.address();
108+
let addressType = 4;
109+
let fd = null;
110+
let address = null;
111+
if (listenConfig.path) {
112+
addressType = -1;
113+
fd = server.address().fd;
114+
address = listenConfig.path;
115+
} else if (listenConfig.hostname) {
116+
const isIp = net.isIP(listenConfig.hostname);
117+
addressType = isIp === 0 ? 4 : isIp;
118+
address = listenConfig.hostname;
119+
}
120+
app.messenger.send('app-start', {
121+
workerPid: process.pid,
122+
address: {
123+
addressType,
124+
address,
125+
port,
126+
fd,
127+
},
128+
}, 'master');
129+
});
111130
}
112131

113132
gracefulExit({

lib/master.js

+17-32
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const terminate = require('./utils/terminate');
2424
const PROTOCOL = Symbol('Master#protocol');
2525
const REAL_PORT = Symbol('Master#real_port');
2626
const APP_ADDRESS = Symbol('Master#appAddress');
27+
const WORKER_ADDRESSES = Symbol('Master#workerAddresses');
2728

2829
class Master extends EventEmitter {
2930

@@ -38,6 +39,7 @@ class Master extends EventEmitter {
3839
* - {Object} [https] https options, { key, cert, ca }, 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();
@@ -60,6 +62,7 @@ class Master extends EventEmitter {
6062
if (process.env.EGG_SERVER_ENV === 'local' || process.env.NODE_ENV === 'development') {
6163
this.logMethod = 'debug';
6264
}
65+
this[WORKER_ADDRESSES] = [];
6366

6467
// get the real framework info
6568
const frameworkPath = this.options.framework;
@@ -84,9 +87,14 @@ class Master extends EventEmitter {
8487

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

9199
const action = 'egg-ready';
92100
this.messenger.send({
@@ -361,17 +369,6 @@ class Master extends EventEmitter {
361369
from: 'app',
362370
});
363371
});
364-
cluster.on('listening', (worker, address) => {
365-
this.messenger.send({
366-
action: 'app-start',
367-
data: {
368-
workerPid: worker.process.pid,
369-
address,
370-
},
371-
to: 'master',
372-
from: 'app',
373-
});
374-
});
375372
}
376373

377374
/**
@@ -547,16 +544,12 @@ class Master extends EventEmitter {
547544
onAppStart(data) {
548545
const worker = this.workerManager.getWorker(data.workerPid);
549546
const address = data.address;
550-
551-
// worker should listen stickyWorkerPort when sticky mode
552-
if (this.options.sticky) {
553-
if (String(address.port) !== String(this.options.stickyWorkerPort)) {
554-
return;
555-
}
556-
// worker should listen REALPORT when not sticky mode
557-
} else if (!isUnixSock(address)
558-
&& (String(address.port) !== String(this[REAL_PORT]))) {
559-
return;
547+
address.protocol = this[PROTOCOL];
548+
if (this.options.nginxSticky) {
549+
this[WORKER_ADDRESSES].push(address);
550+
} else {
551+
address.port = this.options.sticky ? this[REAL_PORT] : address.port;
552+
this[APP_ADDRESS] = getAddress(address);
560553
}
561554

562555
// send message to agent with alive workers
@@ -598,10 +591,6 @@ class Master extends EventEmitter {
598591
worker.disableRefork = false;
599592
}
600593

601-
address.protocol = this[PROTOCOL];
602-
address.port = this.options.sticky ? this[REAL_PORT] : address.port;
603-
this[APP_ADDRESS] = getAddress(address);
604-
605594
if (this.options.sticky) {
606595
this.startMasterSocketServer(err => {
607596
if (err) return this.ready(err);
@@ -720,7 +709,3 @@ function getAddress({
720709
const hostname = address || '127.0.0.1';
721710
return `${protocol}://${hostname}:${port}`;
722711
}
723-
724-
function isUnixSock(address) {
725-
return address.addressType === -1;
726-
}

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

+27-2
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,7 @@ describe('test/master.test.js', () => {
588588
it('should every app worker will get message', function* () {
589589
yield sleep(1000);
590590
// start two workers
591-
app.expect('stdout', /#1 agent get 1 workers \[ \d+ \]/);
592-
app.expect('stdout', /#2 agent get 2 workers \[ \d+, \d+ \]/);
591+
app.expect('stdout', /agent get 2 workers \[ \d+, \d+ \]/);
593592
});
594593

595594
it('agent should get update message after app died', function* () {
@@ -837,6 +836,32 @@ describe('test/master.test.js', () => {
837836
});
838837
});
839838

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

0 commit comments

Comments
 (0)