Sharing Connection among Multi-Process Nodejs
As we know, each Node.js process runs in a single thread. Usually, we split a single process into multiple processes to take advantage of multi-core systems. On the other hand, it brings more system overhead, sush as maintaining more TCP connections between servers.
This module is designed to share connections among multi-process Nodejs.
- Inspired by Leader/Follower pattern.
- Allow ONLY one process "the Leader" to communicate with server. Other processes "the Followers" act as "Proxy" client, and forward all requests to Leader.
- The Leader is selected by "Port Competition". Every process try to listen on a certain port (for example 7777), but ONLY one can occupy the port, then it becomes the Leader, the others become Followers.
- TCP socket connections are maintained between Leader and Followers. And I design a simple communication protocol to exchange data between them.
- If old Leader dies, one of processes will be selected as the new Leader.
normal (without using cluster client)
+--------+ +--------+
| Client | | Client | ...
+--------+ +--------+
| \ / |
| \ / |
| / \ |
| / \ |
+--------+ +--------+
| Server | | Server | ...
+--------+ +--------+
using cluster-client
+-------+
| start |
+---+---+
|
+--------+---------+
__| port competition |__
win / +------------------+ \ lose
/ \
+--------+ tcp conn +----------+
| Leader |<---------------->| Follower |
+--------+ +----------+
|
+--------+
| Client |
+--------+
| \
| \
| \
| \
+--------+ +--------+
| Server | | Server | ...
+--------+ +--------+
- Packet structure
0 1 2 4 12
+-------+-------+---------------+---------------------------------------------------------------+
|version|req/res| reserved | request id |
+-------------------------------+-------------------------------+-------------------------------+
| timeout | connection object length | application object length |
+-------------------------------+---------------------------------------------------------------+
| conn object (JSON format) ... | app object |
+-----------------------------------------------------------+ |
| ... |
+-----------------------------------------------------------------------------------------------+
- Protocol Type
- Register Channel
- Subscribe/Publish
- Invoke
- Sequence diagram
+----------+ +---------------+ +---------+
| Follower | | local server | | Leader |
+----------+ +---------------+ +---------+
| register channel | assign to |
+ -----------------------> | --------------------> |
| | |
| subscribe |
+ ------------------------------------------------> |
| subscribe result |
| <------------------------------------------------ +
| |
| invoke |
+ ------------------------------------------------> |
| invoke result |
| <------------------------------------------------ +
| |
npm install cluster-client --save
Node.js >= 6.0.0 required
'use strict';
const co = require('co');
const Base = require('sdk-base');
const cluster = require('cluster-client');
/**
* Client Example
*/
class YourClient extends Base {
constructor(options) {
super(options);
this.options = options;
this.ready(true);
}
subscribe(reg, listener) {
// subscribe logic
}
publish(reg) {
// publish logic
}
* getData(id) {
// invoke api
}
getDataCallback(id, cb) {
// ...
}
getDataPromise(id) {
// ...
}
}
// create some client instances, but only one instance will connect to server
const client_1 = cluster(YourClient)
.delegate('getData')
.delegate('getDataCallback')
.delegate('getDataPromise')
.create({ foo: 'bar' });
const client_2 = cluster(YourClient)
.delegate('getData')
.delegate('getDataCallback')
.delegate('getDataPromise')
.create({ foo: 'bar' });
const client_3 = cluster(YourClient)
.delegate('getData')
.delegate('getDataCallback')
.delegate('getDataPromise')
.create({ foo: 'bar' });
// subscribe information
client_1.subscribe('some thing', result => console.log(result));
client_2.subscribe('some thing', result => console.log(result));
client_3.subscribe('some thing', result => console.log(result));
// publish data
client_2.publish('some data');
// invoke method
client_3.getDataCallback('some thing', (err, val) => console.log(val));
client_2.getDataPromise('some thing').then(val => console.log(val));
co(function*() {
const ret = yield client_1.getData('some thing');
console.log(ret);
}).catch(err => console.error(err));
delegate(from, to)
: create delegate method,from
is the method name your want to create, andto
have 6 possible values: [subscribe
,unSubscribe
,publish
,invoke
,invokeOneway
,close
], and the default value is invokeoverride(name, value)
: override one propertycreate(…)
create the client instanceclose(client)
close the clientAPIClientBase
a base class to help you create your api client
- DataClient
- Only provider data API, interact with server and maintain persistent connections etc.
- No need to concern
cluster
issue
- APIClient
- Using
cluster-client
to wrap DataClient - Put your bussiness logic here
const Base = require('sdk-base');
class DataClient extends Base {
constructor(options) {
super(options);
this.ready(true);
}
subscribe(info, listener) {
// subscribe data from server
}
publish(info) {
// publish data to server
}
* getData(id) {
// asynchronous API
}
}
const DataClient = require('./your-data-client');
const { APIClientBase } = require('cluster-client');
class APIClient extends APIClientBase {
constructor(options) {
super(options);
this._cache = new Map();
}
get DataClient() {
return DataClient;
}
get delegates() {
return {
getData: 'invoke',
};
}
get clusterOptions() {
return {
name: 'MyClient',
};
}
subscribe(...args) {
return this._client.subscribe(...args);
}
publish(...args) {
return this._client.publish(...args);
}
* getData(id) {
// write your business logic & use data client API
if (this._cache.has(id)) {
return this._cache.get(id);
}
const data = yield this._client.getData(id);
this._cache.set(id, data);
return datal
}
}
|------------------------------------------------|
| APIClient |
| |----------------------------------------|
| | ClusterClient |
| | |---------------------------------|
| | | DataClient |
|-------|------|---------------------------------|
For more information, you can refer to the discussion
Made with contributors-img.