kukuy 1.4.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/.ctagsd/ctagsd.json +954 -0
- package/.ctagsd/file_list.txt +100 -0
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +101 -0
- package/LICENSE +680 -0
- package/README.md +251 -0
- package/captura.png +0 -0
- package/kukuy.js +23 -0
- package/kukuy.workspace +11 -0
- package/package.json +26 -0
- package/restart-balancer.sh +10 -0
- package/routes.json +14 -0
- package/scripts/load_test.py +151 -0
- package/servers.json +19 -0
- package/servers_real.json +19 -0
- package/src/algorithms/AlgorithmManager.js +85 -0
- package/src/algorithms/IPHashAlgorithm.js +131 -0
- package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
- package/src/algorithms/RoundRobinAlgorithm.js +67 -0
- package/src/config/ConfigManager.js +37 -0
- package/src/config/RouteLoader.js +36 -0
- package/src/core/Balancer.js +353 -0
- package/src/core/RoundRobinAlgorithm.js +60 -0
- package/src/core/ServerPool.js +77 -0
- package/src/dashboard/WebDashboard.js +150 -0
- package/src/dashboard/WebSocketServer.js +114 -0
- package/src/extensibility/CachingFilter.js +134 -0
- package/src/extensibility/FilterChain.js +93 -0
- package/src/extensibility/HookManager.js +48 -0
- package/src/protocol/HttpBalancer.js +37 -0
- package/src/protocol/HttpsBalancer.js +47 -0
- package/src/utils/BalancerLogger.js +102 -0
- package/src/utils/HealthChecker.js +51 -0
- package/src/utils/Logger.js +39 -0
- package/src/utils/MetricsCollector.js +82 -0
- package/src/utils/ProfessionalMetrics.js +501 -0
- package/start-iphash.sh +5 -0
- package/start-roundrobin.sh +5 -0
- package/stress-test.js +190 -0
- package/webpage/README.md +17 -0
- package/webpage/index.html +549 -0
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# KUKUY
|
|
2
|
+
|
|
3
|
+
Un balanceador de carga desarrollado en Node.js que distribuye solicitudes entre múltiples servidores backend usando el algoritmo RoundRobin.
|
|
4
|
+
|
|
5
|
+
## Características
|
|
6
|
+
|
|
7
|
+
- Distribución de carga usando algoritmo RoundRobin
|
|
8
|
+
- Soporte para HTTP y HTTPS
|
|
9
|
+
- Configuración mediante variables de entorno
|
|
10
|
+
- Arquitectura modular con soporte para hooks y filtros
|
|
11
|
+
- Verificación de salud de servidores backend
|
|
12
|
+
- Enrutamiento basado en patrones
|
|
13
|
+
- Sistema de logging detallado para servidores online/offline
|
|
14
|
+
- Caching de respuestas para mejorar el rendimiento
|
|
15
|
+
- Panel de control web para monitoreo
|
|
16
|
+
|
|
17
|
+
## Instalación
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuración
|
|
24
|
+
|
|
25
|
+
El balanceador se configura mediante variables de entorno:
|
|
26
|
+
|
|
27
|
+
- `BALANCER_HTTP_PORT`: Puerto para conexiones HTTP (por defecto: 8080)
|
|
28
|
+
- `BALANCER_HTTPS_PORT`: Puerto para conexiones HTTPS (opcional)
|
|
29
|
+
- `CONFIG_FILE_PATH`: Ruta al archivo de configuración de servidores (por defecto: ./servers.json)
|
|
30
|
+
- `ROUTES_FILE_PATH`: Ruta al archivo de configuración de rutas (por defecto: ./routes.json)
|
|
31
|
+
- `LOAD_BALANCING_ALGORITHM`: Algoritmo de balanceo a usar ('roundrobin' o 'iphash') (por defecto: 'roundrobin')
|
|
32
|
+
- `SSL_CERT_PATH`: Ruta al certificado SSL (requerido para HTTPS)
|
|
33
|
+
- `SSL_KEY_PATH`: Ruta a la llave privada SSL (requerido para HTTPS)
|
|
34
|
+
- `HEALTH_CHECK_INTERVAL`: Intervalo para verificación de salud en ms (por defecto: 30000)
|
|
35
|
+
- `LOG_LEVEL`: Nivel de logging (info, warn, error, debug) (por defecto: info)
|
|
36
|
+
- `LOG_FILE_PATH`: Ruta al archivo de logs (por defecto: ./balancer.log)
|
|
37
|
+
|
|
38
|
+
## Archivos de Configuración
|
|
39
|
+
|
|
40
|
+
### servers.json
|
|
41
|
+
Define los servidores backend:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"servers": [
|
|
46
|
+
{
|
|
47
|
+
"url": "http://localhost:3001",
|
|
48
|
+
"weight": 1,
|
|
49
|
+
"tags": ["api"],
|
|
50
|
+
"active": true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"url": "http://localhost:3002",
|
|
54
|
+
"weight": 1,
|
|
55
|
+
"tags": ["api"],
|
|
56
|
+
"active": true
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### routes.json
|
|
63
|
+
Define las reglas de enrutamiento para dirigir solicitudes específicas a grupos particulares de servidores backend basados en patrones de URL:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"routes": [
|
|
68
|
+
{
|
|
69
|
+
"path": "/api/*",
|
|
70
|
+
"methods": ["GET", "POST", "PUT", "DELETE"],
|
|
71
|
+
"target": "api"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"path": "/*",
|
|
75
|
+
"methods": ["*"],
|
|
76
|
+
"target": "web"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Las rutas permiten una distribución selectiva del tráfico:
|
|
83
|
+
- Las rutas definen patrones de URL (usando comodín `*`) y métodos HTTP permitidos
|
|
84
|
+
- El campo `target` especifica la etiqueta de servidores backend a los que se dirigirá la solicitud
|
|
85
|
+
- Por ejemplo, todas las solicitudes que empiezan con `/api/` se enviarán solo a servidores que tengan la etiqueta "api"
|
|
86
|
+
- Las solicitudes que no coincidan con ninguna ruta específica se distribuirán entre todos los servidores disponibles
|
|
87
|
+
|
|
88
|
+
## Uso
|
|
89
|
+
|
|
90
|
+
### Iniciar en modo producción:
|
|
91
|
+
```bash
|
|
92
|
+
npm start
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Iniciar en modo desarrollo:
|
|
96
|
+
```bash
|
|
97
|
+
npm run dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Con variables de entorno personalizadas:
|
|
101
|
+
```bash
|
|
102
|
+
BALANCER_HTTP_PORT=9090 BALANCER_HTTPS_PORT=9443 CONFIG_FILE_PATH=./my-servers.json npm start
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Con algoritmo de balanceo específico:
|
|
106
|
+
```bash
|
|
107
|
+
# Usar algoritmo RoundRobin (predeterminado)
|
|
108
|
+
LOAD_BALANCING_ALGORITHM=roundrobin npm start
|
|
109
|
+
|
|
110
|
+
# Usar algoritmo IPHash
|
|
111
|
+
LOAD_BALANCING_ALGORITHM=iphash npm start
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Scripts de inicio predefinidos:
|
|
115
|
+
|
|
116
|
+
#### Iniciar con algoritmo RoundRobin:
|
|
117
|
+
```bash
|
|
118
|
+
./start-roundrobin.sh
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Iniciar con algoritmo IPHash:
|
|
122
|
+
```bash
|
|
123
|
+
./start-iphash.sh
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Los scripts utilizan la configuración por defecto y el puerto 8080. Puedes modificarlos según tus necesidades.
|
|
127
|
+
|
|
128
|
+
### API del Panel Web
|
|
129
|
+
|
|
130
|
+
El balanceador incluye una API REST para monitoreo en tiempo real:
|
|
131
|
+
|
|
132
|
+
#### Endpoints disponibles:
|
|
133
|
+
- `GET /api/status` - Estado general del balanceador
|
|
134
|
+
- `GET /api/metrics` - Métricas generales de rendimiento
|
|
135
|
+
- `GET /api/servers` - Estado de los servidores backend
|
|
136
|
+
- `GET /api/server-stats` - Estadísticas detalladas por servidor
|
|
137
|
+
|
|
138
|
+
#### Ejemplo de respuesta de estadísticas por servidor:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"serverStats": {
|
|
142
|
+
"1": {
|
|
143
|
+
"id": 1,
|
|
144
|
+
"totalRequests": 10,
|
|
145
|
+
"successfulRequests": 10,
|
|
146
|
+
"failedRequests": 0,
|
|
147
|
+
"totalResponseTime": 1250,
|
|
148
|
+
"minResponseTime": 5,
|
|
149
|
+
"maxResponseTime": 346,
|
|
150
|
+
"avgResponseTime": 125,
|
|
151
|
+
"responseCodes": {"200": 10},
|
|
152
|
+
"lastActive": 1769298297602,
|
|
153
|
+
"responseTimes": [346, 7, 7, 12, 45, 67, 89, 102, 115, 120]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Hooks Disponibles
|
|
160
|
+
|
|
161
|
+
- `onRequestReceived`: Se ejecuta cuando se recibe una solicitud
|
|
162
|
+
- `onServerSelected`: Se ejecuta después de seleccionar un servidor backend
|
|
163
|
+
- `onResponseSent`: Se ejecuta cuando se envía la respuesta al cliente
|
|
164
|
+
- `onServerError`: Se ejecuta cuando ocurre un error con un servidor backend
|
|
165
|
+
|
|
166
|
+
## Filtros Disponibles
|
|
167
|
+
|
|
168
|
+
- `AuthenticationFilter`: Verificación de autenticación
|
|
169
|
+
- `RateLimitFilter`: Control de límite de peticiones
|
|
170
|
+
- `LoggingFilter`: Registro de solicitudes
|
|
171
|
+
- `CachingFilter`: Caché de respuestas
|
|
172
|
+
|
|
173
|
+
## Componentes Implementados
|
|
174
|
+
|
|
175
|
+
### Core
|
|
176
|
+
- `Balancer.js`: Punto de entrada principal del sistema
|
|
177
|
+
- `ServerPool.js`: Gestión del conjunto de servidores backend
|
|
178
|
+
- `RequestHandler.js`: Manejo de solicitudes entrantes (integrado en Balancer.js)
|
|
179
|
+
|
|
180
|
+
### Algoritmos
|
|
181
|
+
- `LoadBalancingAlgorithm.js`: Interfaz base para algoritmos de balanceo
|
|
182
|
+
- `RoundRobinAlgorithm.js`: Implementación del algoritmo RoundRobin como plugin
|
|
183
|
+
- `IPHashAlgorithm.js`: Implementación del algoritmo IPHash como plugin con asociación persistente IP-servidor
|
|
184
|
+
- `AlgorithmManager.js`: Gestor de algoritmos de balanceo con selección dinámica
|
|
185
|
+
|
|
186
|
+
### Configuración
|
|
187
|
+
- `ConfigManager.js`: Lee y procesa variables de entorno
|
|
188
|
+
- `RouteLoader.js`: Carga rutas y paths desde archivos de configuración
|
|
189
|
+
|
|
190
|
+
### Protocolos
|
|
191
|
+
- `HttpBalancer.js`: Manejo de conexiones HTTP
|
|
192
|
+
- `HttpsBalancer.js`: Manejo de conexiones HTTPS
|
|
193
|
+
|
|
194
|
+
### Extensibilidad
|
|
195
|
+
- `HookManager.js`: Integración con `jerk-hooked-lib`
|
|
196
|
+
- `FilterChain.js`: Aplicación de filters a las solicitudes
|
|
197
|
+
|
|
198
|
+
### Utilidades
|
|
199
|
+
- `Logger.js`: Sistema de logging
|
|
200
|
+
- `BalancerLogger.js`: Sistema de logging detallado para servidores online/offline
|
|
201
|
+
- `HealthChecker.js`: Verificación de estado de servidores backend
|
|
202
|
+
- `MetricsCollector.js`: Recolección de métricas de rendimiento
|
|
203
|
+
|
|
204
|
+
## Componentes No Implementados (Mejoras Futuras)
|
|
205
|
+
|
|
206
|
+
### Funcionalidades Avanzadas
|
|
207
|
+
- **Hot Reload de Configuración**: Actualización de configuración sin reinicio (requiere implementación de listeners de cambio de archivos)
|
|
208
|
+
- **API REST para Gestión de Servidores**: Endpoint para añadir/eliminar servidores dinámicamente
|
|
209
|
+
- **Sistema de Alertas**: Notificaciones cuando servidores caen o se recuperan
|
|
210
|
+
- **Soporte para WebSocket**: Balanceo de conexiones WebSocket
|
|
211
|
+
- **Compresión de Respuestas**: Soporte para Gzip/Brotli
|
|
212
|
+
- **Persistencia de Sesión**: Sticky sessions basadas en cookies o IP
|
|
213
|
+
|
|
214
|
+
### Algoritmos de Balanceo Adicionales
|
|
215
|
+
- **Weighted Round Robin**: Balanceo con pesos por servidor
|
|
216
|
+
- **Least Connections**: Enviar a servidor con menos conexiones
|
|
217
|
+
- **IP Hash**: Distribución basada en dirección IP del cliente
|
|
218
|
+
|
|
219
|
+
## Pruebas
|
|
220
|
+
|
|
221
|
+
### Prueba de Estrés
|
|
222
|
+
El proyecto incluye un script de prueba de estrés:
|
|
223
|
+
```bash
|
|
224
|
+
DURATION_SECONDS=30 CONCURRENT_REQUESTS=20 node stress-test.js
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Issues Conocidos
|
|
228
|
+
|
|
229
|
+
### Actualización de Estado de Servidores
|
|
230
|
+
El dashboard no refleja en tiempo real cuando servidores se vuelven offline o se levantan después de iniciar el balanceador. La información de estado de los servidores puede no actualizar inmediatamente cuando un servidor backend cambia de estado.
|
|
231
|
+
|
|
232
|
+
Para más detalles, véase el archivo [ISSUES.md](./ISSUES.md).
|
|
233
|
+
|
|
234
|
+
## Licencia
|
|
235
|
+
|
|
236
|
+
Este proyecto está licenciado bajo la Licencia Pública General GNU (GPL) versión 3 o posterior.
|
|
237
|
+
|
|
238
|
+
Copyright (C) 2026 Desarrollador
|
|
239
|
+
|
|
240
|
+
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
|
|
241
|
+
bajo los términos de la Licencia Pública General GNU publicada por la
|
|
242
|
+
Free Software Foundation, ya sea la versión 3 de la Licencia, o
|
|
243
|
+
(a su elección) cualquier versión posterior.
|
|
244
|
+
|
|
245
|
+
Este programa se distribuye con la esperanza de que sea útil, pero
|
|
246
|
+
SIN NINGUNA GARANTÍA; ni siquiera la garantía implícita de
|
|
247
|
+
COMERCIABILIDAD o IDONEIDAD PARA UN PROPÓSITO PARTICULAR.
|
|
248
|
+
Consulte la Licencia Pública General GNU para obtener más detalles.
|
|
249
|
+
|
|
250
|
+
Debería haber recibido una copia de la Licencia Pública General GNU
|
|
251
|
+
junto con este programa. Si no, consulte <https://www.gnu.org/licenses/>.
|
package/captura.png
ADDED
|
Binary file
|
package/kukuy.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Balancer } = require('./src/core/Balancer');
|
|
4
|
+
|
|
5
|
+
// Iniciar el balanceador
|
|
6
|
+
const balancer = new Balancer();
|
|
7
|
+
balancer.start();
|
|
8
|
+
|
|
9
|
+
console.log('Balanceador RoundRobin iniciado');
|
|
10
|
+
console.log(`Panel web disponible en: http://localhost:${process.env.DASHBOARD_PORT || 8082}`);
|
|
11
|
+
|
|
12
|
+
// Manejar señales de interrupción
|
|
13
|
+
process.on('SIGINT', () => {
|
|
14
|
+
console.log('\nCerrando balanceador...');
|
|
15
|
+
balancer.stop();
|
|
16
|
+
process.exit(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
process.on('SIGTERM', () => {
|
|
20
|
+
console.log('Recibida señal SIGTERM, cerrando...');
|
|
21
|
+
balancer.stop();
|
|
22
|
+
process.exit(0);
|
|
23
|
+
});
|
package/kukuy.workspace
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kukuy",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Balanceador de carga Backend",
|
|
5
|
+
"main": "kukuy.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node kukuy.js",
|
|
8
|
+
"dev": "BALANCER_HTTP_PORT=8080 node kukuy.js",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"load-balancer",
|
|
13
|
+
"round-robin",
|
|
14
|
+
"ip-hash",
|
|
15
|
+
"proxy",
|
|
16
|
+
"http",
|
|
17
|
+
"https"
|
|
18
|
+
],
|
|
19
|
+
"author": "Benjamín Sánchez Cárdenas",
|
|
20
|
+
"license": "GPL-3.0-or-later",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"jerk-hooked-lib": "^2.0.0",
|
|
23
|
+
"ws": "^8.19.0"
|
|
24
|
+
},
|
|
25
|
+
"type": "commonjs"
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Script para matar e iniciar KUKUY con estadísticas por servidor y WebSocket
|
|
3
|
+
|
|
4
|
+
echo "Matando procesos existentes de KUKUY..."
|
|
5
|
+
pkill -f "node kukuy.js" 2>/dev/null || true
|
|
6
|
+
|
|
7
|
+
sleep 2
|
|
8
|
+
|
|
9
|
+
echo "Iniciando KUKUY con estadísticas por servidor y WebSocket..."
|
|
10
|
+
LOAD_BALANCING_ALGORITHM=roundrobin CONFIG_FILE_PATH=./servers_real.json BALANCER_HTTP_PORT=8088 DASHBOARD_PORT=8082 WEBSOCKET_PORT=8084 node kukuy.js
|
package/routes.json
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Script de prueba de carga concurrente para el balanceador
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import requests
|
|
9
|
+
import sys
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
# Configuración
|
|
13
|
+
BASE_URL = "http://localhost:8081"
|
|
14
|
+
ENDPOINT = "/api/endpoints"
|
|
15
|
+
NUM_THREADS = 100 # Número de hilos concurrentes para alta carga
|
|
16
|
+
REQUESTS_PER_THREAD = 6000 # Solicitudes por hilo (100 hilos * 60 solicitudes = 6000 solicitudes totales por segundo)
|
|
17
|
+
DURATION_SECONDS = 120 # Duración de la prueba en segundos (2 minutos)
|
|
18
|
+
TARGET_RPS = 5000 # Objetivo de solicitudes por segundo
|
|
19
|
+
|
|
20
|
+
# Variables globales para métricas
|
|
21
|
+
total_requests = 0
|
|
22
|
+
successful_requests = 0
|
|
23
|
+
failed_requests = 0
|
|
24
|
+
lock = threading.Lock()
|
|
25
|
+
|
|
26
|
+
def make_requests_continuous(thread_id, end_time_target):
|
|
27
|
+
"""Función que realiza solicitudes HTTP continuas hasta alcanzar la duración objetivo"""
|
|
28
|
+
global total_requests, successful_requests, failed_requests
|
|
29
|
+
|
|
30
|
+
thread_start_time = time.time()
|
|
31
|
+
thread_successful = 0
|
|
32
|
+
thread_failed = 0
|
|
33
|
+
|
|
34
|
+
# Calcular el intervalo entre solicitudes para alcanzar el objetivo RPS
|
|
35
|
+
interval = 1.0 / (TARGET_RPS / NUM_THREADS) if NUM_THREADS > 0 else 0.001 # Evitar división por cero
|
|
36
|
+
|
|
37
|
+
while time.time() < end_time_target:
|
|
38
|
+
try:
|
|
39
|
+
response = requests.get(f"{BASE_URL}{ENDPOINT}", timeout=10)
|
|
40
|
+
with lock:
|
|
41
|
+
total_requests += 1
|
|
42
|
+
if response.status_code == 200:
|
|
43
|
+
successful_requests += 1
|
|
44
|
+
thread_successful += 1
|
|
45
|
+
else:
|
|
46
|
+
failed_requests += 1
|
|
47
|
+
thread_failed += 1
|
|
48
|
+
except requests.exceptions.RequestException as e:
|
|
49
|
+
with lock:
|
|
50
|
+
total_requests += 1
|
|
51
|
+
failed_requests += 1
|
|
52
|
+
thread_failed += 1
|
|
53
|
+
if thread_id == 0: # Solo imprimir errores para el primer hilo
|
|
54
|
+
print(f"[Thread-{thread_id}] Error en solicitud: {e}")
|
|
55
|
+
|
|
56
|
+
# Esperar el intervalo calculado para alcanzar el objetivo RPS
|
|
57
|
+
time.sleep(interval)
|
|
58
|
+
|
|
59
|
+
thread_end_time = time.time()
|
|
60
|
+
thread_duration = thread_end_time - thread_start_time
|
|
61
|
+
|
|
62
|
+
print(f"[Thread-{thread_id}] Completado: {thread_successful} exitosas, {thread_failed} fallidas en {thread_duration:.2f}s")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def make_requests(thread_id):
|
|
66
|
+
"""Función original que realiza solicitudes HTTP en un hilo (mantenida por compatibilidad)"""
|
|
67
|
+
global total_requests, successful_requests, failed_requests
|
|
68
|
+
|
|
69
|
+
thread_start_time = time.time()
|
|
70
|
+
thread_successful = 0
|
|
71
|
+
thread_failed = 0
|
|
72
|
+
|
|
73
|
+
for i in range(REQUESTS_PER_THREAD):
|
|
74
|
+
try:
|
|
75
|
+
response = requests.get(f"{BASE_URL}{ENDPOINT}", timeout=10)
|
|
76
|
+
with lock:
|
|
77
|
+
total_requests += 1
|
|
78
|
+
if response.status_code == 200:
|
|
79
|
+
successful_requests += 1
|
|
80
|
+
thread_successful += 1
|
|
81
|
+
else:
|
|
82
|
+
failed_requests += 1
|
|
83
|
+
thread_failed += 1
|
|
84
|
+
except requests.exceptions.RequestException as e:
|
|
85
|
+
with lock:
|
|
86
|
+
total_requests += 1
|
|
87
|
+
failed_requests += 1
|
|
88
|
+
thread_failed += 1
|
|
89
|
+
if thread_id == 0: # Solo imprimir errores para el primer hilo
|
|
90
|
+
print(f"[Thread-{thread_id}] Error en solicitud {i+1}: {e}")
|
|
91
|
+
|
|
92
|
+
thread_end_time = time.time()
|
|
93
|
+
thread_duration = thread_end_time - thread_start_time
|
|
94
|
+
|
|
95
|
+
print(f"[Thread-{thread_id}] Completado: {thread_successful} exitosas, {thread_failed} fallidas en {thread_duration:.2f}s")
|
|
96
|
+
|
|
97
|
+
def run_load_test():
|
|
98
|
+
"""Función principal que ejecuta la prueba de carga"""
|
|
99
|
+
global total_requests, successful_requests, failed_requests
|
|
100
|
+
|
|
101
|
+
print(f"Iniciando prueba de carga...")
|
|
102
|
+
print(f"- URL base: {BASE_URL}")
|
|
103
|
+
print(f"- Endpoint: {ENDPOINT}")
|
|
104
|
+
print(f"- Hilos concurrentes: {NUM_THREADS}")
|
|
105
|
+
print(f"- Solicitudes por hilo: {REQUESTS_PER_THREAD}")
|
|
106
|
+
print(f"- Duración: {DURATION_SECONDS} segundos")
|
|
107
|
+
print(f"- Objetivo RPS: {TARGET_RPS}")
|
|
108
|
+
print("-" * 50)
|
|
109
|
+
|
|
110
|
+
start_time = time.time()
|
|
111
|
+
end_time_target = start_time + DURATION_SECONDS
|
|
112
|
+
|
|
113
|
+
# Crear y arrancar hilos
|
|
114
|
+
threads = []
|
|
115
|
+
for i in range(NUM_THREADS):
|
|
116
|
+
thread = threading.Thread(target=make_requests_continuous, args=(i, end_time_target))
|
|
117
|
+
threads.append(thread)
|
|
118
|
+
thread.start()
|
|
119
|
+
|
|
120
|
+
# Pequeño retraso entre la creación de hilos para evitar congestión
|
|
121
|
+
time.sleep(0.001)
|
|
122
|
+
|
|
123
|
+
# Esperar a que todos los hilos terminen
|
|
124
|
+
for thread in threads:
|
|
125
|
+
thread.join()
|
|
126
|
+
|
|
127
|
+
actual_duration = time.time() - start_time
|
|
128
|
+
|
|
129
|
+
# Mostrar resultados
|
|
130
|
+
print("-" * 50)
|
|
131
|
+
print("RESULTADOS DE LA PRUEBA DE CARGA:")
|
|
132
|
+
print(f"Tiempo total: {actual_duration:.2f} segundos")
|
|
133
|
+
print(f"Solicitudes totales: {total_requests}")
|
|
134
|
+
print(f"Solicitudes exitosas: {successful_requests}")
|
|
135
|
+
print(f"Solicitudes fallidas: {failed_requests}")
|
|
136
|
+
print(f"Tasa de éxito: {(successful_requests/total_requests*100) if total_requests > 0 else 0:.2f}%")
|
|
137
|
+
print(f"RPS Real (Solicitudes por segundo): {total_requests/actual_duration if actual_duration > 0 else 0:.2f}")
|
|
138
|
+
print(f"RPS Objetivo: {TARGET_RPS}")
|
|
139
|
+
|
|
140
|
+
if successful_requests > 0:
|
|
141
|
+
print(f"RPS Exitoso (Solicitudes exitosas por segundo): {successful_requests/actual_duration if actual_duration > 0 else 0:.2f}")
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
try:
|
|
145
|
+
run_load_test()
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
print("\nPrueba interrumpida por el usuario.")
|
|
148
|
+
print(f"Resultados parciales:")
|
|
149
|
+
print(f"- Solicitudes totales: {total_requests}")
|
|
150
|
+
print(f"- Solicitudes exitosas: {successful_requests}")
|
|
151
|
+
print(f"- Solicitudes fallidas: {failed_requests}")
|
package/servers.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"servers": [
|
|
3
|
+
{
|
|
4
|
+
"url": "http://localhost:3001",
|
|
5
|
+
"weight": 1,
|
|
6
|
+
"tags": ["api"]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"url": "http://localhost:3002",
|
|
10
|
+
"weight": 1,
|
|
11
|
+
"tags": ["api"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"url": "http://localhost:3003",
|
|
15
|
+
"weight": 1,
|
|
16
|
+
"tags": ["web"]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"servers": [
|
|
3
|
+
{
|
|
4
|
+
"url": "http://localhost:3434",
|
|
5
|
+
"weight": 1,
|
|
6
|
+
"tags": ["backend"]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"url": "http://localhost:8765",
|
|
10
|
+
"weight": 1,
|
|
11
|
+
"tags": ["backend"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"url": "http://localhost:5445",
|
|
15
|
+
"weight": 1,
|
|
16
|
+
"tags": ["backend"]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const { RoundRobinAlgorithm } = require('./RoundRobinAlgorithm');
|
|
2
|
+
const { IPHashAlgorithm } = require('./IPHashAlgorithm');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gestor de algoritmos de balanceo de carga
|
|
6
|
+
*/
|
|
7
|
+
class AlgorithmManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.algorithms = new Map();
|
|
10
|
+
this.currentAlgorithm = null;
|
|
11
|
+
this.defaultAlgorithm = 'roundrobin';
|
|
12
|
+
|
|
13
|
+
// Registrar algoritmos por defecto
|
|
14
|
+
this.registerAlgorithm(new RoundRobinAlgorithm());
|
|
15
|
+
this.registerAlgorithm(new IPHashAlgorithm());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Registra un nuevo algoritmo
|
|
20
|
+
* @param {LoadBalancingAlgorithm} algorithm - Instancia del algoritmo
|
|
21
|
+
*/
|
|
22
|
+
registerAlgorithm(algorithm) {
|
|
23
|
+
this.algorithms.set(algorithm.getName(), algorithm);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Obtiene un algoritmo por su nombre
|
|
28
|
+
* @param {string} name - Nombre del algoritmo
|
|
29
|
+
* @returns {LoadBalancingAlgorithm|null} Algoritmo o null si no existe
|
|
30
|
+
*/
|
|
31
|
+
getAlgorithm(name) {
|
|
32
|
+
return this.algorithms.get(name) || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Establece el algoritmo actual a usar
|
|
37
|
+
* @param {string} name - Nombre del algoritmo
|
|
38
|
+
* @returns {boolean} True si el algoritmo existe y se estableció
|
|
39
|
+
*/
|
|
40
|
+
setCurrentAlgorithm(name) {
|
|
41
|
+
const algorithm = this.getAlgorithm(name);
|
|
42
|
+
if (algorithm) {
|
|
43
|
+
this.currentAlgorithm = algorithm;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Obtiene el algoritmo actual
|
|
51
|
+
* @returns {LoadBalancingAlgorithm|null} Algoritmo actual o null si no está establecido
|
|
52
|
+
*/
|
|
53
|
+
getCurrentAlgorithm() {
|
|
54
|
+
return this.currentAlgorithm;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Selecciona un servidor usando el algoritmo actual
|
|
59
|
+
* @param {Array} servers - Lista de servidores disponibles
|
|
60
|
+
* @param {Object} requestContext - Contexto de la solicitud
|
|
61
|
+
* @returns {Object|null} Servidor seleccionado o null si no hay disponibles
|
|
62
|
+
*/
|
|
63
|
+
selectServer(servers, requestContext = {}) {
|
|
64
|
+
if (!this.currentAlgorithm) {
|
|
65
|
+
// Si no hay algoritmo actual, usar el predeterminado
|
|
66
|
+
this.setCurrentAlgorithm(this.defaultAlgorithm);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.currentAlgorithm) {
|
|
70
|
+
return this.currentAlgorithm.selectServer(servers, requestContext);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Obtiene la lista de nombres de algoritmos disponibles
|
|
78
|
+
* @returns {Array<string>} Lista de nombres de algoritmos
|
|
79
|
+
*/
|
|
80
|
+
getAvailableAlgorithms() {
|
|
81
|
+
return Array.from(this.algorithms.keys());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { AlgorithmManager };
|