Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/node-modules/cluster-client

Sharing Connection among Multi-Process Nodejs
https://github.com/node-modules/cluster-client

Last synced: about 2 months ago
JSON representation

Sharing Connection among Multi-Process Nodejs

Awesome Lists containing this project

README

        

# cluster-client

Sharing Connection among Multi-Process Nodejs

[![NPM version][npm-image]][npm-url]
[![CI](https://github.com/node-modules/cluster-client/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/cluster-client/actions/workflows/nodejs.yml)
[![Test coverage][codecov-image]][codecov-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
[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
[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

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

## Contributors

|[
gxcsoccer](https://github.com/gxcsoccer)
|[
fengmk2](https://github.com/fengmk2)
|[
shaoshuai0102](https://github.com/shaoshuai0102)
|[
killagu](https://github.com/killagu)
|[
semantic-release-bot](https://github.com/semantic-release-bot)
|[
atian25](https://github.com/atian25)
|
| :---: | :---: | :---: | :---: | :---: | :---: |
[
leoner](https://github.com/leoner)
|[
mansonchor](https://github.com/mansonchor)
|[
sinkhaha](https://github.com/sinkhaha)
|[
limitMe](https://github.com/limitMe)

This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Jun 20 2023 12:29:14 GMT+0800`.