# cluster-client
|
Sharing Connection among Multi-Process Nodejs
|
|
[![NPM version][npm-image]][npm-url]
|
[![build status][travis-image]][travis-url]
|
[![Test coverage][codecov-image]][codecov-url]
|
[![David deps][david-image]][david-url]
|
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
[![npm download][download-image]][download-url]
|
|
[npm-image]: https://img.shields.io/npm/v/cluster-client.svg?style=flat-square
|
[npm-url]: https://npmjs.org/package/cluster-client
|
[travis-image]: https://img.shields.io/travis/node-modules/cluster-client.svg?style=flat-square
|
[travis-url]: https://travis-ci.org/node-modules/cluster-client
|
[codecov-image]: https://codecov.io/gh/node-modules/cluster-client/branch/master/graph/badge.svg
|
[codecov-url]: https://codecov.io/gh/node-modules/cluster-client
|
[david-image]: https://img.shields.io/david/node-modules/cluster-client.svg?style=flat-square
|
[david-url]: https://david-dm.org/node-modules/cluster-client
|
[snyk-image]: https://snyk.io/test/npm/cluster-client/badge.svg?style=flat-square
|
[snyk-url]: https://snyk.io/test/npm/cluster-client
|
[download-image]: https://img.shields.io/npm/dm/cluster-client.svg?style=flat-square
|
[download-url]: https://npmjs.org/package/cluster-client
|
|
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.
|
|
## Theory
|
|
- Inspired by [Leader/Follower pattern](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf).
|
- 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.
|
|
## Diagram
|
|
normal (without using cluster client)
|
```js
|
+--------+ +--------+
|
| Client | | Client | ...
|
+--------+ +--------+
|
| \ / |
|
| \ / |
|
| / \ |
|
| / \ |
|
+--------+ +--------+
|
| Server | | Server | ...
|
+--------+ +--------+
|
|
```
|
|
using cluster-client
|
```js
|
+-------+
|
| start |
|
+---+---+
|
|
|
+--------+---------+
|
__| port competition |__
|
win / +------------------+ \ lose
|
/ \
|
+--------+ tcp conn +----------+
|
| Leader |<---------------->| Follower |
|
+--------+ +----------+
|
|
|
+--------+
|
| Client |
|
+--------+
|
| \
|
| \
|
| \
|
| \
|
+--------+ +--------+
|
| Server | | Server | ...
|
+--------+ +--------+
|
|
```
|
|
## Protocol
|
|
- Packet structure
|
```js
|
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
|
|
```js
|
+----------+ +---------------+ +---------+
|
| Follower | | local server | | Leader |
|
+----------+ +---------------+ +---------+
|
| register channel | assign to |
|
+ -----------------------> | --------------------> |
|
| | |
|
| subscribe |
|
+ ------------------------------------------------> |
|
| subscribe result |
|
| <------------------------------------------------ +
|
| |
|
| invoke |
|
+ ------------------------------------------------> |
|
| invoke result |
|
| <------------------------------------------------ +
|
| |
|
```
|
|
## Install
|
|
```bash
|
$ npm install cluster-client --save
|
```
|
|
Node.js >= 6.0.0 required
|
|
## Usage
|
|
```js
|
'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));
|
```
|
|
## API
|
|
- `delegate(from, to)`:
|
create delegate method, `from` is the method name your want to create, and `to` have 6 possible values: [ `subscribe`, `unSubscribe`, `publish`, `invoke`, `invokeOneway`, `close` ], and the default value is invoke
|
- `override(name, value)`:
|
override one property
|
- `create(…)`
|
create the client instance
|
- `close(client)`
|
close the client
|
- `APIClientBase` a base class to help you create your api client
|
|
## Best Practice
|
|
1. DataClient
|
- Only provider data API, interact with server and maintain persistent connections etc.
|
- No need to concern `cluster` issue
|
2. APIClient
|
- Using `cluster-client` to wrap DataClient
|
- Put your bussiness logic here
|
|
**DataClient**
|
```js
|
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
|
}
|
}
|
```
|
|
**APIClient**
|
```js
|
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
|
}
|
}
|
```
|
|
```js
|
|------------------------------------------------|
|
| APIClient |
|
| |----------------------------------------|
|
| | ClusterClient |
|
| | |---------------------------------|
|
| | | DataClient |
|
|-------|------|---------------------------------|
|
```
|
|
For more information, you can refer to the [discussion](https://github.com/eggjs/egg/issues/322)
|
|
[MIT](LICENSE)
|