{"id":13802518,"url":"https://github.com/node-modules/cluster-client","last_synced_at":"2025-06-14T03:08:09.294Z","repository":{"id":46004613,"uuid":"76217388","full_name":"node-modules/cluster-client","owner":"node-modules","description":"Sharing Connection among Multi-Process Nodejs","archived":false,"fork":false,"pushed_at":"2024-01-24T07:58:43.000Z","size":213,"stargazers_count":132,"open_issues_count":5,"forks_count":15,"subscribers_count":19,"default_branch":"master","last_synced_at":"2024-04-25T15:42:00.710Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/node-modules.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-12T03:03:39.000Z","updated_at":"2024-06-18T13:38:27.795Z","dependencies_parsed_at":"2024-01-13T10:42:23.773Z","dependency_job_id":"dc4586e6-6f91-41ae-9f19-231788c2a45a","html_url":"https://github.com/node-modules/cluster-client","commit_stats":{"total_commits":87,"total_committers":14,"mean_commits":6.214285714285714,"dds":0.735632183908046,"last_synced_commit":"f2112993b57894c235037b440710011251046af2"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcluster-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcluster-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcluster-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcluster-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/node-modules","download_url":"https://codeload.github.com/node-modules/cluster-client/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225218097,"owners_count":17439713,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:01:46.309Z","updated_at":"2025-05-13T13:31:46.352Z","avatar_url":"https://github.com/node-modules.png","language":"JavaScript","readme":"# cluster-client\n\nSharing Connection among Multi-Process Nodejs\n\n[![NPM version][npm-image]][npm-url]\n[![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)\n[![Test coverage][codecov-image]][codecov-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/cluster-client.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/cluster-client\n[codecov-image]: https://codecov.io/gh/node-modules/cluster-client/branch/master/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/node-modules/cluster-client\n[snyk-image]: https://snyk.io/test/npm/cluster-client/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/cluster-client\n[download-image]: https://img.shields.io/npm/dm/cluster-client.svg?style=flat-square\n[download-url]: https://npmjs.org/package/cluster-client\n\nAs 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.\n\nThis module is designed to share connections among multi-process Nodejs.\n\n## Theory\n\n- Inspired by [Leader/Follower pattern](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf).\n- Allow ONLY one process \"the Leader\" to communicate with server. Other processes \"the Followers\" act as \"Proxy\" client, and forward all requests to Leader.\n- 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.\n- TCP socket connections are maintained between Leader and Followers. And I design a simple communication protocol to exchange data between them.\n- If old Leader dies, one of processes will be selected as the new Leader.\n\n## Diagram\n\nnormal (without using cluster client)\n\n```js\n+--------+   +--------+\n| Client |   | Client |   ...\n+--------+   +--------+\n    |  \\     /   |\n    |    \\ /     |\n    |    / \\     |\n    |  /     \\   |\n+--------+   +--------+\n| Server |   | Server |   ...\n+--------+   +--------+\n\n```\n\nusing cluster-client\n\n```js\n             +-------+\n             | start |\n             +---+---+\n                 |\n        +--------+---------+\n      __| port competition |__\nwin /   +------------------+  \\ lose\n   /                           \\\n+--------+     tcp conn     +----------+\n| Leader |\u003c----------------\u003e| Follower |\n+--------+                  +----------+\n    |\n+--------+\n| Client |\n+--------+\n    |  \\\n    |    \\\n    |      \\\n    |        \\\n+--------+   +--------+\n| Server |   | Server |   ...\n+--------+   +--------+\n\n```\n\n## Protocol\n\n- Packet structure\n\n```js\n 0       1       2               4                                                              12\n +-------+-------+---------------+---------------------------------------------------------------+\n |version|req/res|    reserved   |                          request id                           |\n +-------------------------------+-------------------------------+-------------------------------+\n |           timeout             |   connection object length    |   application object length   |\n +-------------------------------+---------------------------------------------------------------+\n |         conn object (JSON format)  ...                    |            app object             |\n +-----------------------------------------------------------+                                   |\n |                                          ...                                                  |\n +-----------------------------------------------------------------------------------------------+\n```\n\n- Protocol Type\n  - Register Channel\n  - Subscribe/Publish\n  - Invoke\n- Sequence diagram\n\n```js\n +----------+             +---------------+          +---------+\n | Follower |             |  local server |          |  Leader |\n +----------+             +---------------+          +---------+\n      |     register channel     |       assign to        |\n      + -----------------------\u003e |  --------------------\u003e |\n      |                          |                        |\n      |                                subscribe          |\n      + ------------------------------------------------\u003e |\n      |       subscribe result                            |\n      | \u003c------------------------------------------------ +\n      |                                                   |\n      |                                 invoke            |\n      + ------------------------------------------------\u003e |\n      |          invoke result                            |\n      | \u003c------------------------------------------------ +\n      |                                                   |\n```\n\n## Install\n\n```bash\nnpm install cluster-client --save\n```\n\nNode.js \u003e= 6.0.0 required\n\n## Usage\n\n```js\n'use strict';\n\nconst co = require('co');\nconst Base = require('sdk-base');\nconst cluster = require('cluster-client');\n\n/**\n * Client Example\n */\nclass YourClient extends Base {\n  constructor(options) {\n    super(options);\n\n    this.options = options;\n    this.ready(true);\n  }\n\n  subscribe(reg, listener) {\n    // subscribe logic\n  }\n\n  publish(reg) {\n    // publish logic\n  }\n\n  * getData(id) {\n    // invoke api\n  }\n\n  getDataCallback(id, cb) {\n    // ...\n  }\n\n  getDataPromise(id) {\n    // ...\n  }\n}\n\n// create some client instances, but only one instance will connect to server\nconst client_1 = cluster(YourClient)\n  .delegate('getData')\n  .delegate('getDataCallback')\n  .delegate('getDataPromise')\n  .create({ foo: 'bar' });\nconst client_2 = cluster(YourClient)\n  .delegate('getData')\n  .delegate('getDataCallback')\n  .delegate('getDataPromise')\n  .create({ foo: 'bar' });\nconst client_3 = cluster(YourClient)\n  .delegate('getData')\n  .delegate('getDataCallback')\n  .delegate('getDataPromise')\n  .create({ foo: 'bar' });\n\n// subscribe information\nclient_1.subscribe('some thing', result =\u003e console.log(result));\nclient_2.subscribe('some thing', result =\u003e console.log(result));\nclient_3.subscribe('some thing', result =\u003e console.log(result));\n\n// publish data\nclient_2.publish('some data');\n\n// invoke method\nclient_3.getDataCallback('some thing', (err, val) =\u003e console.log(val));\nclient_2.getDataPromise('some thing').then(val =\u003e console.log(val));\n\nco(function*() {\n  const ret = yield client_1.getData('some thing');\n  console.log(ret);\n}).catch(err =\u003e console.error(err));\n```\n\n## API\n\n- `delegate(from, to)`:\n  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\n- `override(name, value)`:\n  override one property\n- `create(…)`\n  create the client instance\n- `close(client)`\n  close the client\n- `APIClientBase`  a base class to help you create your api client\n\n## Best Practice\n\n1. DataClient\n\n- Only provider data API, interact with server and maintain persistent connections etc.\n- No need to concern `cluster` issue\n\n1. APIClient\n\n- Using `cluster-client` to wrap DataClient\n- Put your bussiness logic here\n\n### DataClient\n\n```js\nconst Base = require('sdk-base');\n\nclass DataClient extends Base {\n  constructor(options) {\n    super(options);\n    this.ready(true);\n  }\n\n  subscribe(info, listener) {\n    // subscribe data from server\n  }\n\n  publish(info) {\n    // publish data to server\n  }\n\n  * getData(id) {\n    // asynchronous API\n  }\n}\n```\n\n### APIClient\n\n```js\nconst DataClient = require('./your-data-client');\nconst { APIClientBase } = require('cluster-client');\n\nclass APIClient extends APIClientBase {\n  constructor(options) {\n    super(options);\n    this._cache = new Map();\n  }\n  get DataClient() {\n    return DataClient;\n  }\n  get delegates() {\n    return {\n      getData: 'invoke',\n    };\n  }\n  get clusterOptions() {\n    return {\n      name: 'MyClient',\n    };\n  }\n  subscribe(...args) {\n    return this._client.subscribe(...args);\n  }\n  publish(...args) {\n    return this._client.publish(...args);\n  }\n  * getData(id) {\n    // write your business logic \u0026 use data client API\n    if (this._cache.has(id)) {\n      return this._cache.get(id);\n    }\n    const data = yield this._client.getData(id);\n    this._cache.set(id, data);\n    return datal\n  }\n}\n```\n\n```js\n|------------------------------------------------|\n| APIClient                                      |\n|       |----------------------------------------|\n|       | ClusterClient                          |\n|       |      |---------------------------------|\n|       |      | DataClient                      |\n|-------|------|---------------------------------|\n```\n\nFor more information, you can refer to the [discussion](https://github.com/eggjs/egg/issues/322)\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=node-modules/cluster-client)](https://github.com/node-modules/cluster-client/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-modules%2Fcluster-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnode-modules%2Fcluster-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-modules%2Fcluster-client/lists"}