Skip to content

Sharing Connection among Multi-Process Nodejs

License

Notifications You must be signed in to change notification settings

node-modules/cluster-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

82 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cluster-client

Sharing Connection among Multi-Process Nodejs

NPM version build status Test coverage David deps Known Vulnerabilities npm download

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.
  • 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)

+--------+   +--------+
| Client |   | Client |   ...
+--------+   +--------+
    |  \     /   |
    |    \ /     |
    |    / \     |
    |  /     \   |
+--------+   +--------+
| Server |   | Server |   ...
+--------+   +--------+

using cluster-client

             +-------+
             | start |
             +---+---+
                 |
        +--------+---------+
      __| port competition |__
win /   +------------------+  \ lose
   /                           \
+--------+     tcp conn     +----------+
| Leader |<---------------->| Follower |
+--------+                  +----------+
    |
+--------+
| Client |
+--------+
    |  \
    |    \
    |      \
    |        \
+--------+   +--------+
| Server |   | Server |   ...
+--------+   +--------+

Protocol

  • 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                            |
      | <------------------------------------------------ +
      |                                                   |

Install

$ npm install cluster-client --save

Node.js >= 6.0.0 required

Usage

'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
  1. APIClient
  • Using cluster-client to wrap DataClient
  • Put your bussiness logic here

DataClient

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

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

MIT