replica-failover-mongodb-ts 2.0.5 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/Readme.md +95 -6
- package/dist/index.js +5 -0
- package/dist/scripts/dashboard.js +77 -46
- package/package.json +3 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) 2025 João Ito
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
4
|
+
|
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/Readme.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Node Balancer
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/replica-failover-mongodb-ts)
|
|
4
|
+
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
## 🚀 Quick Start (Dashboard CLI)
|
|
9
|
+
|
|
10
|
+
Se você quer apenas rodar o painel de controle visualmente para testar qualquer API:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g replica-failover-mongodb-ts
|
|
14
|
+
node-balancer-dashboard
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## Sobre o Projeto: "Node Balancer"
|
|
4
20
|
|
|
5
21
|
O Node Balancer é uma API escalável construída utilizando Node.js, MongoDB com replica set para alta disponibilidade, e Nginx como balanceador de carga. O sistema foi projetado para garantir resiliência, escalabilidade e alta disponibilidade. A arquitetura permite a adição manual de instâncias backend (Node.js) e garante que, em caso de falhas, o sistema continue operando sem interrupções, com a replicação automática dos dados e balanceamento de carga eficiente.
|
|
@@ -11,11 +27,13 @@ O Node Balancer é uma API escalável construída utilizando Node.js, MongoDB co
|
|
|
11
27
|
## Sumário
|
|
12
28
|
|
|
13
29
|
1. [Tecnologias](#tecnologias)
|
|
14
|
-
2. [Como Rodar o Projeto](#como-rodar-o-projeto)
|
|
15
|
-
3. [
|
|
16
|
-
4. [
|
|
17
|
-
5. [
|
|
18
|
-
6. [
|
|
30
|
+
2. [Como Rodar o Projeto (Dev)](#como-rodar-o-projeto-dev)
|
|
31
|
+
3. [Uso como Biblioteca (Library)](#uso-como-biblioteca-library)
|
|
32
|
+
4. [Visual Dashboard (Painel de Controle)](#visual-dashboard-painel-de-controle)
|
|
33
|
+
5. [Uso Avançado do Dashboard (CLI)](#uso-avançado-do-dashboard-cli)
|
|
34
|
+
6. [Testes e Automação (Chaos Testing)](#testes-e-automação-chaos-testing)
|
|
35
|
+
7. [Documentação Detalhada](#documentação-detalhada)
|
|
36
|
+
8. [Configuração Manual (Referência)](#configuração-manual-referência)
|
|
19
37
|
|
|
20
38
|
---
|
|
21
39
|
|
|
@@ -31,7 +49,7 @@ O Node Balancer utiliza as seguintes tecnologias:
|
|
|
31
49
|
|
|
32
50
|
---
|
|
33
51
|
|
|
34
|
-
## Como Rodar o Projeto
|
|
52
|
+
## Como Rodar o Projeto (Dev)
|
|
35
53
|
|
|
36
54
|
### Pré-requisitos
|
|
37
55
|
- Docker e Docker Compose instalados.
|
|
@@ -61,6 +79,33 @@ O Node Balancer utiliza as seguintes tecnologias:
|
|
|
61
79
|
|
|
62
80
|
---
|
|
63
81
|
|
|
82
|
+
## Uso como Biblioteca (Library)
|
|
83
|
+
|
|
84
|
+
Você pode usar o gerenciador de conexões resiliente deste projeto em sua própria aplicação Node.js.
|
|
85
|
+
|
|
86
|
+
1. **Instale a lib:**
|
|
87
|
+
```bash
|
|
88
|
+
npm install replica-failover-mongodb-ts
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
2. **Importe e use:**
|
|
92
|
+
```typescript
|
|
93
|
+
import { ConnectionManager } from 'replica-failover-mongodb-ts';
|
|
94
|
+
|
|
95
|
+
const db = new ConnectionManager({
|
|
96
|
+
nodes: [
|
|
97
|
+
'mongodb://mongo1:27017/mydb',
|
|
98
|
+
'mongodb://mongo2:27017/mydb'
|
|
99
|
+
],
|
|
100
|
+
healthCheckIntervalMs: 5000
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await db.init();
|
|
104
|
+
const myCollection = db.getDb().collection('users');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
64
109
|
## Visual Dashboard (Painel de Controle)
|
|
65
110
|
|
|
66
111
|
Para uma experiência visual e interativa, utilize o nosso Dashboard via Terminal (TUI). Ele permite monitorar a topologia do cluster, gráficos de latência e controlar os nós (Stop/Start) manualmente.
|
|
@@ -106,6 +151,35 @@ Ao realizar testes de carga ou criar usuários via dashboard, a API retornará r
|
|
|
106
151
|
|
|
107
152
|
---
|
|
108
153
|
|
|
154
|
+
## Uso Avançado do Dashboard (CLI)
|
|
155
|
+
|
|
156
|
+
O dashboard pode ser configurado para monitorar **qualquer API** e **qualquer cluster MongoDB**, não apenas o deste projeto.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
node-balancer-dashboard [opções]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Opções Disponíveis
|
|
163
|
+
|
|
164
|
+
| Flag | Descrição | Padrão |
|
|
165
|
+
| :--- | :--- | :--- |
|
|
166
|
+
| `--api-url` | URL da API para testar latência/requests | `http://localhost:3000/api/users` |
|
|
167
|
+
| `--nodes` | Lista de URIs do MongoDB (separados por vírgula) | `mongodb://localhost:27017...` |
|
|
168
|
+
| `--no-docker` | Desabilita controles do Docker (para clusters remotos) | `false` |
|
|
169
|
+
|
|
170
|
+
### Exemplo Real
|
|
171
|
+
|
|
172
|
+
Testando uma API de produção sem acesso ao Docker local:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
node-balancer-dashboard \
|
|
176
|
+
--api-url https://api.minhaempresa.com/health \
|
|
177
|
+
--nodes mongodb://mongo-prod-1:27017,mongodb://mongo-prod-2:27017 \
|
|
178
|
+
--no-docker
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
109
183
|
## Testes e Automação (Chaos Testing)
|
|
110
184
|
|
|
111
185
|
Se preferir rodar apenas o script de teste sem o dashboard visual:
|
|
@@ -185,3 +259,18 @@ Se você está utilizando o **MongoDB replica set**, certifique-se de que o repl
|
|
|
185
259
|
```javascript
|
|
186
260
|
rs.status();
|
|
187
261
|
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Autor
|
|
266
|
+
|
|
267
|
+
| [<img src="https://github.com/JoaoIto.png" width="100px;" alt="João Ito"/>](https://github.com/JoaoIto) |
|
|
268
|
+
| :---: |
|
|
269
|
+
| **João Ito** |
|
|
270
|
+
| [GitHub](https://github.com/JoaoIto) • [Email](mailto:joaovictorpfr@gmail.com) |
|
|
271
|
+
|
|
272
|
+
Feito com ❤️ por João Ito. Entre em contato!
|
|
273
|
+
|
|
274
|
+
## Licença
|
|
275
|
+
|
|
276
|
+
Este projeto está licenciado sob a licença ISC - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectionManager = void 0;
|
|
4
|
+
var connectionManager_1 = require("./config/connectionManager");
|
|
5
|
+
Object.defineProperty(exports, "ConnectionManager", { enumerable: true, get: function () { return connectionManager_1.ConnectionManager; } });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
4
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -17,13 +18,21 @@ const blessed_contrib_1 = __importDefault(require("blessed-contrib"));
|
|
|
17
18
|
const mongodb_1 = require("mongodb");
|
|
18
19
|
const http_1 = __importDefault(require("http"));
|
|
19
20
|
const child_process_1 = require("child_process");
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
// Parse Args
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
function getArg(flag, def) {
|
|
24
|
+
const idx = args.indexOf(flag);
|
|
25
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : def;
|
|
26
|
+
}
|
|
27
|
+
const API_URL = getArg('--api-url', 'http://localhost:3000/api/users');
|
|
28
|
+
const NODES_ARG = getArg('--nodes', 'mongodb://localhost:27017/node-balancer,mongodb://localhost:27018/node-balancer,mongodb://localhost:27019/node-balancer');
|
|
29
|
+
const DOCKER_CONTAINERS_ARG = getArg('--docker-containers', 'mongo1,mongo2,mongo3');
|
|
30
|
+
const NO_DOCKER = args.includes('--no-docker');
|
|
31
|
+
const NODES = NODES_ARG.split(',').map((uri, i) => ({
|
|
32
|
+
name: `node${i + 1}`,
|
|
33
|
+
uri: uri.trim()
|
|
34
|
+
}));
|
|
35
|
+
const DOCKER_NODES = DOCKER_CONTAINERS_ARG.split(',').map(n => n.trim());
|
|
27
36
|
// Screen Setup
|
|
28
37
|
const screen = blessed_1.default.screen({
|
|
29
38
|
smartCSR: true,
|
|
@@ -64,11 +73,11 @@ const controls = grid.set(6, 8, 6, 4, blessed_1.default.list, {
|
|
|
64
73
|
items: [
|
|
65
74
|
'RUN CHAOS DEMO (Auto)',
|
|
66
75
|
'SEND BATCH (2 POST + 1 GET)',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
...(NO_DOCKER ? [] : [
|
|
77
|
+
'STOP PRIMARY',
|
|
78
|
+
...DOCKER_NODES.map(n => `START ${n.toUpperCase()}`),
|
|
79
|
+
'START STACK'
|
|
80
|
+
]),
|
|
72
81
|
'EXIT'
|
|
73
82
|
]
|
|
74
83
|
});
|
|
@@ -155,7 +164,7 @@ function updateTopology() {
|
|
|
155
164
|
try {
|
|
156
165
|
const client = new mongodb_1.MongoClient(node.uri, { serverSelectionTimeoutMS: 500, directConnection: true });
|
|
157
166
|
yield client.connect();
|
|
158
|
-
const db = client.db('node-balancer');
|
|
167
|
+
const db = client.db('node-balancer'); // Assuming DB name is consistent or part of URI, but here hardcoded for now or could be arg
|
|
159
168
|
const hello = yield db.command({ hello: 1 });
|
|
160
169
|
count = (yield db.collection('users').countDocuments()).toString();
|
|
161
170
|
yield client.close();
|
|
@@ -186,34 +195,43 @@ function runChaosDemo() {
|
|
|
186
195
|
// Phase 1
|
|
187
196
|
log('Phase 1: Healthy State');
|
|
188
197
|
yield runBatch();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (primary) {
|
|
192
|
-
log(`💥 Stopping PRIMARY: ${primary}`);
|
|
193
|
-
try {
|
|
194
|
-
(0, child_process_1.execSync)(`docker stop ${primary}`);
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
log('Error stopping node');
|
|
198
|
-
}
|
|
198
|
+
if (NO_DOCKER) {
|
|
199
|
+
log('Skipping Chaos (No Docker Mode)');
|
|
199
200
|
}
|
|
200
201
|
else {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
// Chaos
|
|
203
|
+
const primaryName = yield getPrimary(); // returns node1, node2...
|
|
204
|
+
// We need to map node name to container name if they differ, but here we assume index matching or we need smarter logic
|
|
205
|
+
// For simplicity in this generic version, let's try to find the container name based on index
|
|
206
|
+
const primaryIndex = NODES.findIndex(n => n.name === primaryName);
|
|
207
|
+
const containerName = primaryIndex !== -1 ? DOCKER_NODES[primaryIndex] : null;
|
|
208
|
+
if (containerName) {
|
|
209
|
+
log(`💥 Stopping PRIMARY: ${containerName}`);
|
|
210
|
+
try {
|
|
211
|
+
(0, child_process_1.execSync)(`docker stop ${containerName}`);
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
log('Error stopping node');
|
|
215
|
+
}
|
|
214
216
|
}
|
|
215
|
-
|
|
216
|
-
log('
|
|
217
|
+
else {
|
|
218
|
+
log('No Primary found to stop!');
|
|
219
|
+
}
|
|
220
|
+
// Phase 2
|
|
221
|
+
log('Phase 2: Failover State');
|
|
222
|
+
yield sleep(2000);
|
|
223
|
+
yield runBatch();
|
|
224
|
+
// Recovery
|
|
225
|
+
log('Waiting 5s before recovery...');
|
|
226
|
+
yield sleep(5000);
|
|
227
|
+
if (containerName) {
|
|
228
|
+
log(`♻️ Restarting ${containerName}`);
|
|
229
|
+
try {
|
|
230
|
+
(0, child_process_1.execSync)(`docker start ${containerName}`);
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
log('Error starting node');
|
|
234
|
+
}
|
|
217
235
|
}
|
|
218
236
|
}
|
|
219
237
|
// Phase 3
|
|
@@ -233,11 +251,15 @@ controls.on('select', (item, index) => __awaiter(void 0, void 0, void 0, functio
|
|
|
233
251
|
runBatch();
|
|
234
252
|
}
|
|
235
253
|
else if (cmd.includes('STOP PRIMARY')) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
if (NO_DOCKER)
|
|
255
|
+
return log('Docker disabled.');
|
|
256
|
+
const pName = yield getPrimary();
|
|
257
|
+
const pIdx = NODES.findIndex(n => n.name === pName);
|
|
258
|
+
const container = pIdx !== -1 ? DOCKER_NODES[pIdx] : null;
|
|
259
|
+
if (container) {
|
|
260
|
+
log(`Stopping ${container}...`);
|
|
239
261
|
try {
|
|
240
|
-
(0, child_process_1.execSync)(`docker stop ${
|
|
262
|
+
(0, child_process_1.execSync)(`docker stop ${container}`);
|
|
241
263
|
log('Stopped.');
|
|
242
264
|
}
|
|
243
265
|
catch (e) {
|
|
@@ -248,11 +270,15 @@ controls.on('select', (item, index) => __awaiter(void 0, void 0, void 0, functio
|
|
|
248
270
|
log('No Primary found.');
|
|
249
271
|
}
|
|
250
272
|
}
|
|
251
|
-
else if (cmd.includes('START MONGO')) {
|
|
252
|
-
|
|
253
|
-
|
|
273
|
+
else if (cmd.includes('START MONGO') || cmd.includes('START NODE')) { // Generic match
|
|
274
|
+
if (NO_DOCKER)
|
|
275
|
+
return log('Docker disabled.');
|
|
276
|
+
// Extract container name from string "START MONGO1"
|
|
277
|
+
const parts = cmd.split(' ');
|
|
278
|
+
const container = parts[1].toLowerCase(); // mongo1
|
|
279
|
+
log(`Starting ${container}...`);
|
|
254
280
|
try {
|
|
255
|
-
(0, child_process_1.execSync)(`docker start ${
|
|
281
|
+
(0, child_process_1.execSync)(`docker start ${container}`);
|
|
256
282
|
log('Started.');
|
|
257
283
|
}
|
|
258
284
|
catch (e) {
|
|
@@ -260,6 +286,8 @@ controls.on('select', (item, index) => __awaiter(void 0, void 0, void 0, functio
|
|
|
260
286
|
}
|
|
261
287
|
}
|
|
262
288
|
else if (cmd.includes('START STACK')) {
|
|
289
|
+
if (NO_DOCKER)
|
|
290
|
+
return log('Docker disabled.');
|
|
263
291
|
log('Starting stack...');
|
|
264
292
|
try {
|
|
265
293
|
(0, child_process_1.execSync)('docker-compose up -d');
|
|
@@ -278,6 +306,9 @@ setInterval(updateTopology, 2000);
|
|
|
278
306
|
// Init
|
|
279
307
|
controls.focus();
|
|
280
308
|
log('Control Center Ready.');
|
|
309
|
+
if (NO_DOCKER)
|
|
310
|
+
log('Docker Control: DISABLED');
|
|
311
|
+
log(`API: ${API_URL}`);
|
|
281
312
|
updateTopology();
|
|
282
313
|
screen.render();
|
|
283
314
|
// Exit
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replica-failover-mongodb-ts",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "O sistema replica um dos conceitos básicos da arquitetura de sistemas distribuído, na qual ele tem uma API em express.js com TypeScript, e usando o mongoDB com replicaSET e failover, consegue automáticamente garantir disponibilidade e redundância de dados das requisições.",
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"types": "dist/
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "nodemon --exec ts-node src/server.ts",
|
|
9
9
|
"build": "tsc",
|