kukuy 1.4.0 → 1.5.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/.env.ssl ADDED
@@ -0,0 +1,22 @@
1
+ # Archivo de configuración para Kukuy en modo SSL
2
+
3
+ # Puerto para el servidor HTTPS
4
+ BALANCER_HTTPS_PORT=8443
5
+
6
+ # Ruta al certificado SSL
7
+ SSL_CERT_PATH=certs/auto/certificate.crt
8
+
9
+ # Ruta a la llave privada SSL
10
+ SSL_KEY_PATH=certs/auto/private.key
11
+
12
+ # Opcional: Puerto para el servidor HTTP (descomentar para habilitar)
13
+ # BALANCER_HTTP_PORT=8080
14
+
15
+ # Opcional: Habilitar modo HTTP junto con HTTPS
16
+ # ENABLE_HTTP=true
17
+
18
+ # Otros ajustes de configuración
19
+ LOG_LEVEL=info
20
+ HEALTH_CHECK_INTERVAL=30000
21
+ CONFIG_FILE_PATH=./servers_real.json
22
+ ROUTES_FILE_PATH=./routes.json
package/CHANGELOG.md CHANGED
@@ -98,4 +98,28 @@
98
98
 
99
99
  ### Known Issues
100
100
  - El dashboard no refleja en tiempo real cuando servidores se vuelven offline o se levantan después de iniciar el balanceador
101
- - La información de estado de los servidores puede no actualizar inmediatamente cuando un servidor backend cambia de estado
101
+ - La información de estado de los servidores puede no actualizar inmediatamente cuando un servidor backend cambia de estado
102
+
103
+ ## [Versión 1.5.0] - 2026-01-25
104
+
105
+ ### Added
106
+ - Soporte completo para SSL/TLS en el balanceador
107
+ - Scripts de inicio para operación en modo SSL (start-ssl.sh y start-ssl-config.sh)
108
+ - Archivo de configuración específico para SSL (.env.ssl)
109
+ - Documentación detallada sobre configuración SSL (README-SSL.md)
110
+ - Generación de certificados SSL autofirmados en directorio certs/auto
111
+ - Soporte para archivos de configuración personalizados (servers_real.json)
112
+ - Configuración flexible mediante variables de entorno para SSL
113
+ - Integración con ngrok para exposición segura de servicios SSL
114
+ - Validación de configuración SSL en tiempo de inicio
115
+
116
+ ### Changed
117
+ - Actualización de la documentación principal para incluir instrucciones de SSL
118
+ - Mejora en la flexibilidad de configuración del balanceador
119
+ - Actualización de los scripts de inicio para usar servers_real.json por defecto
120
+ - Mejora en la gestión de variables de entorno para diferentes modos de operación
121
+
122
+ ### Fixed
123
+ - Problemas de configuración SSL en el módulo HttpsBalancer
124
+ - Manejo de variables de entorno para certificados SSL
125
+ - Configuración predeterminada para archivos de servidores en modo SSL
package/README-SSL.md ADDED
@@ -0,0 +1,165 @@
1
+ # Kukuy - Balanceador de Carga con SSL
2
+
3
+ ![Kukuy Logo](kukuu1.webp)
4
+
5
+ Este documento explica cómo configurar e iniciar el balanceador Kukuy con soporte SSL/TLS.
6
+
7
+ ## Información del Proyecto
8
+
9
+ - **Página Oficial**: [https://bsanchez.unaux.com/](https://bsanchez.unaux.com/)
10
+ - **Repositorio Oficial**: [https://gitlab.com/bytedogssyndicate1/kukuy](https://gitlab.com/bytedogssyndicate1/kukuy)
11
+
12
+ ## Contenido
13
+
14
+ - [Requisitos](#requisitos)
15
+ - [Configuración de SSL](#configuración-de-ssl)
16
+ - [Inicio del Balanceador con SSL](#inicio-del-balanceador-con-ssl)
17
+ - [Uso de ngrok con SSL](#uso-de-ngrok-con-ssl)
18
+ - [Scripts Disponibles](#scripts-disponibles)
19
+ - [Variables de Entorno](#variables-de-entorno)
20
+ - [Configuración de Servidores](#configuración-de-servidores)
21
+
22
+ ## Requisitos
23
+
24
+ - Node.js instalado
25
+ - OpenSSL (para generar certificados autofirmados)
26
+ - Opcional: ngrok para exponer servicios locales a Internet
27
+
28
+ ## Configuración de SSL
29
+
30
+ ### Generar Certificados Autofirmados
31
+
32
+ Para generar certificados SSL autofirmados, ejecuta el siguiente comando:
33
+
34
+ ```bash
35
+ mkdir -p certs/auto
36
+ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout certs/auto/private.key -out certs/auto/certificate.crt -subj "/C=ES/ST=Madrid/L=Madrid/O=Kukuy/OU=IT Department/CN=localhost"
37
+ ```
38
+
39
+ Esto creará los siguientes archivos:
40
+ - `certs/auto/certificate.crt`: El certificado SSL
41
+ - `certs/auto/private.key`: La llave privada
42
+
43
+ ### Usar Certificados Existentes
44
+
45
+ Si tienes certificados SSL válidos emitidos por una CA, colócalos en el directorio `certs/auto` o en cualquier ubicación de tu elección y actualiza las rutas en las variables de entorno.
46
+
47
+ ## Inicio del Balanceador con SSL
48
+
49
+ ### Método 1: Variables de Entorno Directas
50
+
51
+ ```bash
52
+ SSL_CERT_PATH=certs/auto/certificate.crt SSL_KEY_PATH=certs/auto/private.key BALANCER_HTTPS_PORT=8443 CONFIG_FILE_PATH=./servers_real.json node kukuy.js
53
+ ```
54
+
55
+ ### Método 2: Usando Scripts Preparados
56
+
57
+ El proyecto incluye scripts para facilitar el inicio:
58
+
59
+ ```bash
60
+ # Iniciar con SSL usando el script básico
61
+ ./start-ssl.sh
62
+
63
+ # Iniciar con SSL usando el script de configuración
64
+ ./start-ssl-config.sh
65
+ ```
66
+
67
+ Por defecto, ambos scripts utilizan el archivo `servers_real.json` para la configuración de servidores.
68
+
69
+ ## Uso de ngrok con SSL
70
+
71
+ Para exponer tu balanceador SSL a Internet a través de ngrok:
72
+
73
+ 1. Instala ngrok desde https://ngrok.com/download
74
+ 2. Ejecuta el siguiente comando:
75
+
76
+ ```bash
77
+ ngrok tls 8443
78
+ ```
79
+
80
+ Esto creará un túnel seguro que redirigirá las conexiones SSL a tu balanceador local en el puerto 8443.
81
+
82
+ Alternativamente, puedes usar un túnel HTTP si prefieres que ngrok maneje la terminación SSL:
83
+
84
+ ```bash
85
+ ngrok http https://localhost:8443
86
+ ```
87
+
88
+ ## Scripts Disponibles
89
+
90
+ ### `start-ssl.sh`
91
+
92
+ Script básico para iniciar Kukuy en modo SSL con valores predeterminados:
93
+ - Puerto HTTPS: 8443
94
+ - Archivo de certificado: `certs/auto/certificate.crt`
95
+ - Archivo de llave privada: `certs/auto/private.key`
96
+ - Archivo de configuración de servidores: `servers_real.json`
97
+
98
+ Opcionalmente, puedes habilitar el modo HTTP simultáneamente:
99
+ ```bash
100
+ ENABLE_HTTP=true ./start-ssl.sh
101
+ ```
102
+
103
+ ### `start-ssl-config.sh`
104
+
105
+ Script que carga la configuración desde el archivo `.env.ssl`. Este archivo contiene todas las variables de entorno necesarias para la operación del balanceador con SSL.
106
+
107
+ ## Variables de Entorno
108
+
109
+ Las siguientes variables de entorno controlan la configuración SSL:
110
+
111
+ - `SSL_CERT_PATH`: Ruta al archivo de certificado SSL
112
+ - `SSL_KEY_PATH`: Ruta al archivo de llave privada SSL
113
+ - `BALANCER_HTTPS_PORT`: Puerto en el que escuchará el servidor HTTPS
114
+ - `BALANCER_HTTP_PORT`: Puerto opcional para el servidor HTTP (si se desea mantener ambos)
115
+ - `CONFIG_FILE_PATH`: Ruta al archivo de configuración de servidores
116
+ - `ROUTES_FILE_PATH`: Ruta al archivo de configuración de rutas
117
+ - `LOG_LEVEL`: Nivel de logging (info, debug, warn, error)
118
+ - `HEALTH_CHECK_INTERVAL`: Intervalo para verificaciones de salud de servidores (en milisegundos)
119
+
120
+ ## Configuración de Servidores
121
+
122
+ El balanceador puede leer la configuración de servidores desde archivos JSON. Por defecto, este setup utiliza `servers_real.json` que contiene:
123
+
124
+ ```json
125
+ {
126
+ "servers": [
127
+ {
128
+ "url": "http://localhost:3434",
129
+ "weight": 1,
130
+ "tags": ["backend"]
131
+ },
132
+ {
133
+ "url": "http://localhost:8765",
134
+ "weight": 1,
135
+ "tags": ["backend"]
136
+ },
137
+ {
138
+ "url": "http://localhost:5445",
139
+ "weight": 1,
140
+ "tags": ["backend"]
141
+ }
142
+ ]
143
+ }
144
+ ```
145
+
146
+ Puedes modificar este archivo para apuntar a tus servidores backend reales o crear nuevos archivos de configuración según sea necesario.
147
+
148
+ ## Panel de Control
149
+
150
+ Una vez iniciado el balanceador, el panel de control web estará disponible en:
151
+ - HTTP: http://localhost:8082
152
+
153
+ ## Solución de Problemas
154
+
155
+ ### Certificados SSL Autofirmados
156
+
157
+ Cuando uses certificados autofirmados, los navegadores mostrarán advertencias de seguridad. Esto es normal y esperado. Para producción, utiliza certificados firmados por una Autoridad de Certificación (CA) reconocida.
158
+
159
+ ### Puertos Bloqueados
160
+
161
+ Asegúrate de que los puertos que deseas usar (por defecto 8443 para HTTPS y 8082 para el panel) estén disponibles y no sean usados por otros servicios.
162
+
163
+ ### Conexión a Servidores Backend
164
+
165
+ Verifica que los servidores backend definidos en el archivo de configuración estén accesibles desde la máquina donde corre el balanceador.
package/README.md CHANGED
@@ -1,7 +1,28 @@
1
1
  # KUKUY
2
2
 
3
+ ![Kukuy Logo](kukuu1.webp)
4
+
3
5
  Un balanceador de carga desarrollado en Node.js que distribuye solicitudes entre múltiples servidores backend usando el algoritmo RoundRobin.
4
6
 
7
+ ## Información del Proyecto
8
+
9
+ - **Página Oficial**: [https://bsanchez.unaux.com/](https://bsanchez.unaux.com/)
10
+ - **Repositorio Oficial**: [https://gitlab.com/bytedogssyndicate1/kukuy](https://gitlab.com/bytedogssyndicate1/kukuy)
11
+
12
+ ## Instalación
13
+
14
+ Para instalar Kukuy globalmente en tu sistema:
15
+
16
+ ```bash
17
+ npm install -g kukuy
18
+ ```
19
+
20
+ O para instalarlo localmente en tu proyecto:
21
+
22
+ ```bash
23
+ npm install kukuy
24
+ ```
25
+
5
26
  ## Características
6
27
 
7
28
  - Distribución de carga usando algoritmo RoundRobin
package/kukuu1.webp ADDED
Binary file
package/kukuy.js CHANGED
@@ -1,23 +1,69 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { Balancer } = require('./src/core/Balancer');
4
+ const packageJson = require('./package.json');
5
+
6
+ // Medir tiempo de inicio
7
+ const startTime = Date.now();
8
+ const startMemory = process.memoryUsage();
4
9
 
5
10
  // Iniciar el balanceador
6
11
  const balancer = new Balancer();
7
12
  balancer.start();
8
13
 
9
- console.log('Balanceador RoundRobin iniciado');
10
- console.log(`Panel web disponible en: http://localhost:${process.env.DASHBOARD_PORT || 8082}`);
14
+ // Calcular tiempo de inicio y uso de memoria
15
+ const startupTime = Date.now() - startTime;
16
+ const endMemory = process.memoryUsage();
17
+ const memoryUsed = endMemory.heapUsed - startMemory.heapUsed;
18
+
19
+ // Colores para la consola
20
+ const colors = {
21
+ reset: '\x1b[0m',
22
+ bright: '\x1b[1m',
23
+ dim: '\x1b[2m',
24
+ underscore: '\x1b[4m',
25
+ blink: '\x1b[5m',
26
+ reverse: '\x1b[7m',
27
+ hidden: '\x1b[8m',
28
+
29
+ fgBlack: '\x1b[30m',
30
+ fgRed: '\x1b[31m',
31
+ fgGreen: '\x1b[32m',
32
+ fgYellow: '\x1b[33m',
33
+ fgBlue: '\x1b[34m',
34
+ fgMagenta: '\x1b[35m',
35
+ fgCyan: '\x1b[36m',
36
+ fgWhite: '\x1b[37m',
37
+
38
+ bgBlack: '\x1b[40m',
39
+ bgRed: '\x1b[41m',
40
+ bgGreen: '\x1b[42m',
41
+ bgYellow: '\x1b[43m',
42
+ bgBlue: '\x1b[44m',
43
+ bgMagenta: '\x1b[45m',
44
+ bgCyan: '\x1b[46m',
45
+ bgWhite: '\x1b[47m'
46
+ };
47
+
48
+ // Mostrar información con colores
49
+ console.log(`${colors.fgYellow}${colors.bright}╔══════════════════════════════════════════════════════════════╗${colors.reset}`);
50
+ console.log(`${colors.fgYellow}${colors.bright}║ KUKUY BALANCEADOR ║${colors.reset}`);
51
+ console.log(`${colors.fgYellow}${colors.bright}╚══════════════════════════════════════════════════════════════╝${colors.reset}`);
52
+ console.log(`${colors.fgYellow}Versión:${colors.reset} ${colors.fgGreen}${colors.bright}${packageJson.version}${colors.reset}`);
53
+ console.log(`${colors.fgYellow}Tiempo de inicio:${colors.reset} ${colors.fgGreen}${colors.bright}${startupTime} ms${colors.reset}`);
54
+ console.log(`${colors.fgYellow}Memoria consumida:${colors.reset} ${colors.fgGreen}${colors.bright}${Math.round(memoryUsed / 1024 / 1024 * 100) / 100} MB${colors.reset}`);
55
+ console.log(`${colors.fgYellow}${colors.bright}Balanceador RoundRobin iniciado${colors.reset}`);
56
+ console.log(`${colors.fgBlue}Panel web disponible en: http://localhost:${process.env.DASHBOARD_PORT || 8082}${colors.reset}`);
11
57
 
12
58
  // Manejar señales de interrupción
13
59
  process.on('SIGINT', () => {
14
- console.log('\nCerrando balanceador...');
60
+ console.log(`\n${colors.fgRed}Cerrando balanceador...${colors.reset}`);
15
61
  balancer.stop();
16
62
  process.exit(0);
17
63
  });
18
64
 
19
65
  process.on('SIGTERM', () => {
20
- console.log('Recibida señal SIGTERM, cerrando...');
66
+ console.log(`${colors.fgRed}Recibida señal SIGTERM, cerrando...${colors.reset}`);
21
67
  balancer.stop();
22
68
  process.exit(0);
23
- });
69
+ });
@@ -0,0 +1,152 @@
1
+ #!/bin/bash
2
+
3
+ # Script para optimizar MariaDB y liberar espacio en disco
4
+ # Autor: Sistema de Optimización
5
+ # Fecha: $(date +%Y-%m-%d)
6
+
7
+ set -e # Salir si ocurre algún error
8
+
9
+ echo "=== Iniciando optimización de MariaDB y liberación de espacio ==="
10
+
11
+ # Crear directorio para backups
12
+ BACKUP_DIR="/home/bds/kukuy/backups-$(date +%Y%m%d_%H%M%S)"
13
+ mkdir -p "$BACKUP_DIR"
14
+
15
+ echo "Directorio de backup: $BACKUP_DIR"
16
+
17
+ # 1. BACKUP DE ARCHIVOS DE CONFIGURACIÓN DE MARIADB
18
+ echo ""
19
+ echo "1. Realizando backup de archivos de configuración..."
20
+
21
+ CONFIG_FILES=(
22
+ "/etc/mysql/mariadb.conf.d/50-server.cnf"
23
+ "/etc/mysql/mariadb.conf.d/50-client.cnf"
24
+ "/etc/mysql/mariadb.conf.d/50-mysql-clients.cnf"
25
+ "/etc/mysql/my.cnf"
26
+ "/etc/mysql/debian-start"
27
+ )
28
+
29
+ for config_file in "${CONFIG_FILES[@]}"; do
30
+ if [ -f "$config_file" ]; then
31
+ cp "$config_file" "$BACKUP_DIR/$(basename "$config_file")"
32
+ echo " - Backup de $config_file realizado"
33
+ else
34
+ echo " - $config_file no encontrado"
35
+ fi
36
+ done
37
+
38
+ # 2. LIBERAR ESPACIO EN DISCO
39
+ echo ""
40
+ echo "2. Liberando espacio en disco..."
41
+
42
+ # Limpiar paquetes desactualizados
43
+ echo " - Limpiando paquetes desactualizados..."
44
+ sudo apt autoremove -y 2>/dev/null || echo " - No se pudo ejecutar apt autoremove (sin privilegios)"
45
+
46
+ # Limpiar cache de paquetes
47
+ echo " - Limpiando cache de paquetes..."
48
+ sudo apt autoclean 2>/dev/null || echo " - No se pudo ejecutar apt autoclean (sin privilegios)"
49
+
50
+ # Limpiar logs antiguos (sin sudo)
51
+ echo " - Buscando logs grandes en /tmp..."
52
+ find /tmp -name "*.log" -size +100M -delete 2>/dev/null || echo " - No se pudieron eliminar logs en /tmp (sin privilegios)"
53
+
54
+ # Limpiar historial de comandos si es necesario
55
+ echo " - Verificando tamaño de archivos de historial..."
56
+ if [ -f "$HOME/.bash_history" ] && [ $(stat -c%s "$HOME/.bash_history" 2>/dev/null || echo 0) -gt 100000 ]; then
57
+ tail -n 1000 "$HOME/.bash_history" > "$HOME/.bash_history.tmp" && mv "$HOME/.bash_history.tmp" "$HOME/.bash_history"
58
+ echo " Archivo de historial reducido"
59
+ fi
60
+
61
+ # 3. OPTIMIZACIÓN DE CONFIGURACIÓN DE MARIADB
62
+ echo ""
63
+ echo "3. Optimizando configuración de MariaDB..."
64
+
65
+ SERVER_CONFIG="/etc/mysql/mariadb.conf.d/50-server.cnf"
66
+
67
+ if [ -f "$SERVER_CONFIG" ]; then
68
+ # Crear copia de seguridad temporal
69
+ TEMP_CONFIG="$BACKUP_DIR/50-server.cnf.optimized"
70
+ cp "$SERVER_CONFIG" "$TEMP_CONFIG"
71
+
72
+ # Aplicar optimizaciones (solo si se puede escribir)
73
+ if [ -w "$SERVER_CONFIG" ]; then
74
+ echo " - Aplicando optimizaciones a $SERVER_CONFIG"
75
+
76
+ # Ajustar tamaño del buffer pool de InnoDB (si hay suficiente RAM)
77
+ TOTAL_RAM=$(free -m | awk 'NR==2{print $2}')
78
+ if [ $TOTAL_RAM -gt 2048 ]; then
79
+ # Si hay más de 2GB de RAM, asignar 25% a buffer pool
80
+ BUFFER_POOL_SIZE=$((TOTAL_RAM * 25 / 100))
81
+ BUFFER_POOL_BYTES=$((BUFFER_POOL_SIZE * 1024 * 1024))
82
+ else
83
+ # Si hay menos de 2GB, asignar 128MB
84
+ BUFFER_POOL_BYTES=134217728
85
+ fi
86
+
87
+ # Ajustar configuración de logs para reducir uso de disco
88
+ sudo tee -a "$SERVER_CONFIG" > /dev/null << EOF
89
+
90
+ # Optimizaciones añadidas por optimize-mariadb.sh
91
+ [mysqld]
92
+ # Ajustar tamaño del buffer pool de InnoDB
93
+ innodb_buffer_pool_size = ${BUFFER_POOL_BYTES}
94
+
95
+ # Reducir tamaño de logs para ahorrar espacio en disco
96
+ innodb_log_file_size = 67108864 # 64MB
97
+ innodb_log_files_in_group = 2
98
+
99
+ # Configurar rotación de logs binarios
100
+ expire_logs_days = 3
101
+ max_binlog_size = 100M
102
+
103
+ # Limitar tamaño de queries temporales
104
+ tmp_table_size = 64M
105
+ max_heap_table_size = 64M
106
+
107
+ # Ajustar timeouts para liberar conexiones rápidamente
108
+ wait_timeout = 300
109
+ interactive_timeout = 300
110
+
111
+ # Habilitar slow query log con umbral razonable
112
+ slow_query_log = 1
113
+ long_query_time = 2
114
+ slow_query_log_file = /var/log/mysql/slow.log
115
+ EOF
116
+ echo " Configuración optimizada aplicada"
117
+ else
118
+ echo " No se tienen permisos para modificar $SERVER_CONFIG"
119
+ echo " Las optimizaciones recomendadas son:"
120
+ echo " - innodb_buffer_pool_size = [25% de RAM disponible]"
121
+ echo " - innodb_log_file_size = 64M"
122
+ echo " - expire_logs_days = 3"
123
+ echo " - wait_timeout = 300"
124
+ fi
125
+ else
126
+ echo " - $SERVER_CONFIG no encontrado"
127
+ fi
128
+
129
+ # 4. ANALIZAR USO DE ESPACIO EN MARIADB
130
+ echo ""
131
+ echo "4. Analizando uso de espacio en bases de datos..."
132
+
133
+ # Obtener información de tamaño de bases de datos
134
+ mysql -u root -e "
135
+ SELECT
136
+ table_schema AS 'Base de Datos',
137
+ ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Tamaño (MB)'
138
+ FROM information_schema.tables
139
+ GROUP BY table_schema
140
+ ORDER BY (data_length + index_length) DESC;
141
+ " 2>/dev/null || echo " - No se pudo acceder a la información de bases de datos"
142
+
143
+ # 5. RECOMENDACIONES FINALES
144
+ echo ""
145
+ echo "=== Recomendaciones ==="
146
+ echo "1. Revisa el directorio de backup: $BACKUP_DIR"
147
+ echo "2. Reinicia MariaDB para aplicar cambios de configuración:"
148
+ echo " sudo systemctl restart mariadb"
149
+ echo "3. Considera eliminar archivos innecesarios en el sistema"
150
+ echo "4. Monitorea el uso de disco regularmente con 'df -h'"
151
+ echo ""
152
+ echo "=== Optimización completada ==="
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kukuy",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Balanceador de carga Backend",
5
5
  "main": "kukuy.js",
6
6
  "scripts": {
@@ -22,5 +22,10 @@
22
22
  "jerk-hooked-lib": "^2.0.0",
23
23
  "ws": "^8.19.0"
24
24
  },
25
- "type": "commonjs"
25
+ "type": "commonjs",
26
+ "homepage": "https://bsanchez.unaux.com/",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://gitlab.com/bytedogssyndicate1/kukuy"
30
+ }
26
31
  }
@@ -9,6 +9,10 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
9
9
  super();
10
10
  // Mapa para almacenar la asociación persistente IP -> índice de servidor
11
11
  this.ipToServerIndexMap = new Map();
12
+ // Caché para servidores filtrados
13
+ this.filteredServersCache = new Map();
14
+ this.cacheTimestamp = 0;
15
+ this.cacheValidity = 1000; // 1 segundo de validez de caché
12
16
  }
13
17
 
14
18
  /**
@@ -22,10 +26,23 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
22
26
  return null;
23
27
  }
24
28
 
25
- // Filtrar servidores activos y saludables
26
- const availableServers = servers.filter(server =>
27
- server.active !== false && server.healthy && server.failedAttempts < 5
28
- );
29
+ // Obtener timestamp actual
30
+ const now = Date.now();
31
+
32
+ // Verificar si la caché es válida
33
+ let availableServers;
34
+ if (now - this.cacheTimestamp > this.cacheValidity || !this.filteredServersCache.has(servers)) {
35
+ // Filtrar servidores activos y saludables
36
+ availableServers = servers.filter(server =>
37
+ server.active !== false && server.healthy && server.failedAttempts < 5
38
+ );
39
+
40
+ // Actualizar caché
41
+ this.filteredServersCache.set(servers, availableServers);
42
+ this.cacheTimestamp = now;
43
+ } else {
44
+ availableServers = this.filteredServersCache.get(servers);
45
+ }
29
46
 
30
47
  if (availableServers.length === 0) {
31
48
  return null;
@@ -43,7 +60,6 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
43
60
  const cachedServer = availableServers[serverIndex];
44
61
  if (cachedServer && cachedServer.healthy && cachedServer.active) {
45
62
  // Servidor sigue disponible, usarlo
46
- console.log(`IPHash: IP=${clientIP} reutilizando servidor previamente asignado (índice ${serverIndex})`);
47
63
  return cachedServer;
48
64
  } else {
49
65
  // Servidor ya no está disponible, eliminar la asociación
@@ -61,9 +77,6 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
61
77
 
62
78
  const selectedServer = availableServers[serverIndex];
63
79
 
64
- // Log para mostrar el mapa de hash
65
- console.log(`IPHash: IP=${clientIP}, Hash=${hash}, Asignando servidor índice ${serverIndex} (ID: ${selectedServer.id}, URL: ${selectedServer.url})`);
66
-
67
80
  return selectedServer;
68
81
  }
69
82
 
@@ -104,12 +117,11 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
104
117
  */
105
118
  simpleHash(str) {
106
119
  let hash = 0;
107
- for (let i = 0; i < str.length; i++) {
108
- const char = str.charCodeAt(i);
109
- hash = ((hash << 5) - hash) + char;
110
- hash = hash & hash; // Convertir a 32-bit integer
120
+ const len = str.length;
121
+ for (let i = 0; i < len; i++) {
122
+ hash = ((hash << 5) - hash) + str.charCodeAt(i);
111
123
  }
112
- return Math.abs(hash);
124
+ return Math.abs(hash | 0); // Convertir a 32-bit integer
113
125
  }
114
126
 
115
127
  /**
@@ -8,6 +8,10 @@ class RoundRobinAlgorithm extends LoadBalancingAlgorithm {
8
8
  constructor() {
9
9
  super();
10
10
  this.currentIndex = 0;
11
+ // Caché para servidores filtrados
12
+ this.filteredServersCache = new Map();
13
+ this.cacheTimestamp = 0;
14
+ this.cacheValidity = 1000; // 1 segundo de validez de caché
11
15
  }
12
16
 
13
17
  /**
@@ -21,39 +25,33 @@ class RoundRobinAlgorithm extends LoadBalancingAlgorithm {
21
25
  return null;
22
26
  }
23
27
 
24
- // Filtrar servidores activos y saludables
25
- const availableServers = servers.filter(server =>
26
- server.active !== false && server.healthy && server.failedAttempts < 5
27
- );
28
+ // Obtener timestamp actual
29
+ const now = Date.now();
30
+
31
+ // Verificar si la caché es válida
32
+ let availableServers;
33
+ if (now - this.cacheTimestamp > this.cacheValidity || !this.filteredServersCache.has(servers)) {
34
+ // Filtrar servidores activos y saludables
35
+ availableServers = servers.filter(server =>
36
+ server.active !== false && server.healthy && server.failedAttempts < 5
37
+ );
38
+
39
+ // Actualizar caché
40
+ this.filteredServersCache.set(servers, availableServers);
41
+ this.cacheTimestamp = now;
42
+ } else {
43
+ availableServers = this.filteredServersCache.get(servers);
44
+ }
28
45
 
29
46
  if (availableServers.length === 0) {
30
47
  return null;
31
48
  }
32
49
 
33
- // Intentar encontrar un servidor disponible usando round robin
34
- // Si el servidor actual no está disponible, buscar el siguiente disponible
35
- for (let i = 0; i < availableServers.length; i++) {
36
- const candidateIndex = (this.currentIndex + i) % availableServers.length;
37
- const candidateServer = availableServers[candidateIndex];
38
-
39
- // Verificar si el servidor candidato está realmente disponible
40
- if (this.isServerAvailable(candidateServer)) {
41
- this.currentIndex = candidateIndex + 1; // Preparar para la próxima selección
42
- return candidateServer;
43
- }
44
- }
45
-
46
- // Si ningún servidor está disponible, devolver null
47
- return null;
48
- }
50
+ // Obtener servidor usando round robin sin bucle
51
+ const server = availableServers[this.currentIndex % availableServers.length];
52
+ this.currentIndex = (this.currentIndex + 1) % availableServers.length;
49
53
 
50
- // Método para verificar si un servidor está disponible
51
- isServerAvailable(server) {
52
- // Un servidor está disponible si:
53
- // 1. Está marcado como healthy
54
- // 2. Está activo
55
- // 3. No ha superado el número máximo de intentos fallidos
56
- return server.healthy && server.active && server.failedAttempts < 5;
54
+ return server;
57
55
  }
58
56
 
59
57
  /**
@@ -178,11 +178,12 @@ class Balancer {
178
178
 
179
179
  if (matchedRoute && matchedRoute.target) {
180
180
  // Si hay una ruta específica, usar solo los servidores definidos para esa ruta
181
- const routeServers = this.serverPool.getHealthyServers().filter(server =>
182
- server.tags && server.tags.includes(matchedRoute.target)
183
- );
184
- if (routeServers.length > 0) {
185
- return this.algorithmManager.selectServer(routeServers, requestContext);
181
+ const routeServers = this.serverPool.getServersByTag(matchedRoute.target);
182
+ if (routeServers && routeServers.length > 0) {
183
+ const healthyRouteServers = routeServers.filter(server => server.healthy && server.active);
184
+ if (healthyRouteServers.length > 0) {
185
+ return this.algorithmManager.selectServer(healthyRouteServers, requestContext);
186
+ }
186
187
  }
187
188
  }
188
189
 
@@ -197,10 +198,10 @@ class Balancer {
197
198
  let retryCount = 0;
198
199
  const maxRetries = this.serverPool.getHealthyServers().length; // Máximo de reintentos según servidores saludables
199
200
 
200
- // Intentar enviar la solicitud con reintento si el servidor inicial falla
201
- while (retryCount <= maxRetries) {
202
- try {
203
- const parsedUrl = url.parse(targetServer.url);
201
+ // Función auxiliar para manejar la solicitud
202
+ const makeRequest = (server) => {
203
+ return new Promise((resolve, reject) => {
204
+ const parsedUrl = url.parse(server.url);
204
205
  const targetOptions = {
205
206
  hostname: parsedUrl.hostname,
206
207
  port: parsedUrl.port,
@@ -209,133 +210,134 @@ class Balancer {
209
210
  headers: clientReq.headers
210
211
  };
211
212
 
212
- const httpModule = targetServer.protocol === 'https:' ? require('https') : require('http');
213
+ const httpModule = server.protocol === 'https:' ? require('https') : require('http');
213
214
  const proxyReq = httpModule.request(targetOptions);
214
215
 
215
216
  // Registrar intento de conexión a servidor target
216
- this.balancerLogger.logTargetSelection(targetServer, 'round_robin');
217
+ this.balancerLogger.logTargetSelection(server, 'round_robin');
217
218
 
218
219
  // Enviar cuerpo de la solicitud
219
220
  clientReq.pipe(proxyReq);
220
221
 
221
- // Resolver la promesa cuando se complete la solicitud
222
- return new Promise((resolve, reject) => {
223
- proxyReq.on('response', (serverRes) => {
224
- // Copiar headers de la respuesta
225
- clientRes.writeHead(serverRes.statusCode, serverRes.headers);
226
-
227
- // Enviar cuerpo de la respuesta al cliente
228
- serverRes.pipe(clientRes);
229
-
230
- // Calcular tiempo de respuesta
231
- const responseTime = Date.now() - startTime;
232
-
233
- // Registrar métricas profesionales
234
- this.metricsCollector.recordRequest(
235
- clientReq.method,
236
- clientReq.url,
237
- targetServer.id,
238
- responseTime,
239
- serverRes.statusCode,
240
- serverRes.statusCode < 400
241
- );
242
-
243
- // Registrar servidor online
244
- this.balancerLogger.logOnlineTarget(targetServer, {
245
- url: clientReq.url,
246
- method: clientReq.method,
247
- responseTime,
248
- statusCode: serverRes.statusCode
249
- });
250
-
251
- // Actualizar estado del servidor en las métricas
252
- this.metricsCollector.updateServerStatus(targetServer.id, 'online');
253
-
254
- // Emitir hook cuando se envía la respuesta
255
- this.hookManager.executeHooks('onResponseSent', {
256
- req: clientReq,
257
- res: clientRes,
258
- serverRes,
259
- server: targetServer,
260
- responseTime
261
- });
262
-
263
- resolve(); // Resolver la promesa al completar la solicitud
222
+ proxyReq.on('response', (serverRes) => {
223
+ // Copiar headers de la respuesta
224
+ clientRes.writeHead(serverRes.statusCode, serverRes.headers);
225
+
226
+ // Enviar cuerpo de la respuesta al cliente
227
+ serverRes.pipe(clientRes);
228
+
229
+ // Calcular tiempo de respuesta
230
+ const responseTime = Date.now() - startTime;
231
+
232
+ // Registrar métricas profesionales
233
+ this.metricsCollector.recordRequest(
234
+ clientReq.method,
235
+ clientReq.url,
236
+ server.id,
237
+ responseTime,
238
+ serverRes.statusCode,
239
+ serverRes.statusCode < 400
240
+ );
241
+
242
+ // Registrar servidor online
243
+ this.balancerLogger.logOnlineTarget(server, {
244
+ url: clientReq.url,
245
+ method: clientReq.method,
246
+ responseTime,
247
+ statusCode: serverRes.statusCode
264
248
  });
265
249
 
266
- proxyReq.on('error', async (err) => {
267
- const responseTime = Date.now() - startTime;
268
-
269
- this.logger.error(`Error conectando con servidor ${targetServer.url}: ${err.message}`);
270
-
271
- // Registrar servidor offline
272
- this.balancerLogger.logOfflineTarget(targetServer, err, {
273
- url: clientReq.url,
274
- method: clientReq.method,
275
- responseTime
276
- });
277
-
278
- // Actualizar estado del servidor en las métricas
279
- this.metricsCollector.updateServerStatus(targetServer.id, 'offline');
280
-
281
- // Registrar métricas profesionales para error
282
- this.metricsCollector.recordRequest(
283
- clientReq.method,
284
- clientReq.url,
285
- targetServer.id,
286
- responseTime,
287
- 502,
288
- false
289
- );
290
-
291
- // Marcar servidor como fallido
292
- this.serverPool.markServerAsFailed(targetServer.id);
293
-
294
- // Emitir hook cuando ocurre un error con el servidor
295
- await this.hookManager.executeHooks('onServerError', {
296
- req: clientReq,
297
- res: clientRes,
298
- server: targetServer,
299
- error: err,
300
- responseTime
301
- });
302
-
303
- // Si hay más servidores disponibles, reintentar con otro servidor
304
- if (retryCount < maxRetries) {
305
- retryCount++;
306
-
307
- // Obtener un nuevo servidor objetivo (excluyendo el que falló)
308
- const availableServers = this.serverPool.getHealthyServers().filter(s => s.id !== targetServer.id);
309
- if (availableServers.length > 0) {
310
- // Seleccionar un nuevo servidor
311
- targetServer = availableServers[(retryCount - 1) % availableServers.length];
312
- console.log(`Reintentando con servidor alternativo: ${targetServer.url}`);
313
-
314
- // Volver a intentar la solicitud con el nuevo servidor
315
- setTimeout(() => {
316
- this.proxyRequest(clientReq, clientRes, targetServer).then(resolve).catch(reject);
317
- }, 10); // Pequeña pausa antes de reintentar
318
-
319
- return; // Salir para evitar resolver/rechazar la promesa dos veces
320
- }
321
- }
250
+ // Actualizar estado del servidor en las métricas
251
+ this.metricsCollector.updateServerStatus(server.id, 'online');
322
252
 
323
- // Si no hay más reintentos posibles, enviar error al cliente
324
- if (!clientRes.headersSent) {
325
- clientRes.writeHead(502);
326
- clientRes.end('Bad Gateway');
327
- }
253
+ // Emitir hook cuando se envía la respuesta
254
+ this.hookManager.executeHooks('onResponseSent', {
255
+ req: clientReq,
256
+ res: clientRes,
257
+ serverRes,
258
+ server,
259
+ responseTime
260
+ });
261
+
262
+ resolve(); // Resolver la promesa al completar la solicitud
263
+ });
264
+
265
+ proxyReq.on('error', async (err) => {
266
+ const responseTime = Date.now() - startTime;
328
267
 
329
- resolve(); // Resolver para evitar múltiples llamadas
268
+ this.logger.error(`Error conectando con servidor ${server.url}: ${err.message}`);
269
+
270
+ // Registrar servidor offline
271
+ this.balancerLogger.logOfflineTarget(server, err, {
272
+ url: clientReq.url,
273
+ method: clientReq.method,
274
+ responseTime
330
275
  });
276
+
277
+ // Actualizar estado del servidor en las métricas
278
+ this.metricsCollector.updateServerStatus(server.id, 'offline');
279
+
280
+ // Registrar métricas profesionales para error
281
+ this.metricsCollector.recordRequest(
282
+ clientReq.method,
283
+ clientReq.url,
284
+ server.id,
285
+ responseTime,
286
+ 502,
287
+ false
288
+ );
289
+
290
+ // Marcar servidor como fallido
291
+ this.serverPool.markServerAsFailed(server.id);
292
+
293
+ // Emitir hook cuando ocurre un error con el servidor
294
+ await this.hookManager.executeHooks('onServerError', {
295
+ req: clientReq,
296
+ res: clientRes,
297
+ server,
298
+ error: err,
299
+ responseTime
300
+ });
301
+
302
+ // Si hay más servidores disponibles, reintentar con otro servidor
303
+ if (retryCount < maxRetries) {
304
+ retryCount++;
305
+
306
+ // Obtener un nuevo servidor objetivo (excluyendo los que ya fallaron)
307
+ const availableServers = this.serverPool.getHealthyServers().filter(s => s.id !== server.id);
308
+ if (availableServers.length > 0) {
309
+ // Seleccionar un nuevo servidor
310
+ const nextServer = availableServers[(retryCount - 1) % availableServers.length];
311
+ console.log(`Reintentando con servidor alternativo: ${nextServer.url}`);
312
+
313
+ // Volver a intentar la solicitud con el nuevo servidor
314
+ setTimeout(() => {
315
+ makeRequest(nextServer).then(resolve).catch(reject);
316
+ }, 10); // Pequeña pausa antes de reintentar
317
+
318
+ return; // Salir para evitar resolver/rechazar la promesa dos veces
319
+ }
320
+ }
321
+
322
+ // Si no hay más reintentos posibles, enviar error al cliente
323
+ if (!clientRes.headersSent) {
324
+ clientRes.writeHead(502);
325
+ clientRes.end('Bad Gateway');
326
+ }
327
+
328
+ resolve(); // Resolver para evitar múltiples llamadas
331
329
  });
332
- } catch (error) {
333
- console.error('Error en proxyRequest:', error);
334
- if (!clientRes.headersSent) {
335
- clientRes.writeHead(500);
336
- clientRes.end('Internal Server Error');
337
- }
338
- return;
330
+ });
331
+ };
332
+
333
+ // Intentar enviar la solicitud con reintento si el servidor inicial falla
334
+ try {
335
+ await makeRequest(targetServer);
336
+ } catch (error) {
337
+ console.error('Error en proxyRequest:', error);
338
+ if (!clientRes.headersSent) {
339
+ clientRes.writeHead(500);
340
+ clientRes.end('Internal Server Error');
339
341
  }
340
342
  }
341
343
  }
@@ -3,8 +3,12 @@ const { HealthChecker } = require('../utils/HealthChecker');
3
3
  class ServerPool {
4
4
  constructor() {
5
5
  this.servers = [];
6
+ this.healthyServers = []; // Caché de servidores saludables
7
+ this.serversById = new Map(); // Acceso rápido por ID
8
+ this.serversByTag = new Map(); // Caché de servidores por tag
6
9
  this.healthChecker = new HealthChecker();
7
10
  this.nextId = 1;
11
+ this.cacheValid = false; // Indicador de validez de caché
8
12
  }
9
13
 
10
14
  addServer(serverConfig) {
@@ -22,6 +26,12 @@ class ServerPool {
22
26
  };
23
27
 
24
28
  this.servers.push(server);
29
+ this.serversById.set(server.id, server);
30
+
31
+ // Actualizar cachés
32
+ this.updateTagCache(server);
33
+ this.cacheValid = false;
34
+
25
35
  return server;
26
36
  }
27
37
 
@@ -36,19 +46,39 @@ class ServerPool {
36
46
  }
37
47
 
38
48
  getHealthyServers() {
39
- return this.servers.filter(server => server.healthy && server.active);
49
+ if (!this.cacheValid) {
50
+ this.healthyServers = this.servers.filter(server => server.healthy && server.active);
51
+ this.cacheValid = true;
52
+ }
53
+ return this.healthyServers;
40
54
  }
41
55
 
42
56
  getServersByTag(tag) {
43
- return this.servers.filter(server => server.tags.includes(tag));
57
+ if (this.serversByTag.has(tag)) {
58
+ return this.serversByTag.get(tag);
59
+ }
60
+ return [];
61
+ }
62
+
63
+ // Actualiza la caché de servidores por tag
64
+ updateTagCache(server) {
65
+ for (const tag of server.tags) {
66
+ if (!this.serversByTag.has(tag)) {
67
+ this.serversByTag.set(tag, []);
68
+ }
69
+ this.serversByTag.get(tag).push(server);
70
+ }
44
71
  }
45
72
 
46
73
  markServerAsFailed(serverId) {
47
- const server = this.servers.find(s => s.id === serverId);
74
+ const server = this.serversById.get(serverId);
48
75
  if (server) {
49
76
  server.failedAttempts++;
50
77
  server.healthy = false;
51
-
78
+
79
+ // Invalidar caché porque un servidor cambió de estado
80
+ this.cacheValid = false;
81
+
52
82
  // Programar verificación de salud después de un tiempo
53
83
  setTimeout(() => {
54
84
  this.healthChecker.checkServerHealth(server)
@@ -57,11 +87,17 @@ class ServerPool {
57
87
  server.healthy = true;
58
88
  server.failedAttempts = 0;
59
89
  server.lastChecked = Date.now();
90
+
91
+ // Invalidar caché porque un servidor cambió de estado
92
+ this.cacheValid = false;
60
93
  } else {
61
94
  server.lastChecked = Date.now();
62
95
  // Si ha fallado demasiadas veces, mantenerlo inactivo
63
96
  if (server.failedAttempts > 5) {
64
97
  server.active = false;
98
+
99
+ // Invalidar caché porque un servidor cambió de estado
100
+ this.cacheValid = false;
65
101
  }
66
102
  }
67
103
  });
@@ -70,7 +106,12 @@ class ServerPool {
70
106
  }
71
107
 
72
108
  getServerById(serverId) {
73
- return this.servers.find(server => server.id === serverId);
109
+ return this.serversById.get(serverId);
110
+ }
111
+
112
+ // Método para invalidar la caché manualmente si es necesario
113
+ invalidateCache() {
114
+ this.cacheValid = false;
74
115
  }
75
116
  }
76
117
 
@@ -15,34 +15,40 @@ class HealthChecker {
15
15
  port: parsedUrl.port,
16
16
  path: '/health', // Ruta estándar para verificación de salud
17
17
  method: 'GET',
18
- timeout: this.timeout
18
+ timeout: this.timeout,
19
+ // Agregar headers para identificar la solicitud de health check
20
+ headers: {
21
+ 'User-Agent': 'Kukuy-Health-Check/1.0'
22
+ }
19
23
  };
20
24
 
21
25
  return new Promise((resolve) => {
22
- const request = server.protocol === 'https:'
26
+ const request = server.protocol === 'https:'
23
27
  ? https.request(options)
24
28
  : http.request(options);
25
29
 
26
30
  request.on('response', (res) => {
31
+ // Consumir el cuerpo de la respuesta para liberar recursos
32
+ res.resume();
33
+
27
34
  // Considerar saludable si obtenemos una respuesta exitosa
28
35
  const isHealthy = res.statusCode >= 200 && res.statusCode < 400;
29
36
  resolve(isHealthy);
30
37
  });
31
38
 
32
39
  request.on('error', (err) => {
33
- console.error(`Error verificando salud de ${server.url}: ${err.message}`);
40
+ // No imprimir errores de health check en consola para evitar spam
34
41
  resolve(false);
35
42
  });
36
43
 
37
44
  request.on('timeout', () => {
38
- console.error(`Timeout verificando salud de ${server.url}`);
45
+ // No imprimir errores de timeout de health check en consola
39
46
  resolve(false);
40
47
  });
41
48
 
42
49
  request.end();
43
50
  });
44
51
  } catch (error) {
45
- console.error(`Error en la verificación de salud: ${error.message}`);
46
52
  return false;
47
53
  }
48
54
  }
@@ -122,12 +122,16 @@ class ProfessionalMetrics {
122
122
 
123
123
  // Actualizar métricas de rendimiento
124
124
  this.metrics.performance.totalResponseTime += responseTime;
125
- this.metrics.performance.minResponseTime = Math.min(this.metrics.performance.minResponseTime, responseTime);
126
- this.metrics.performance.maxResponseTime = Math.max(this.metrics.performance.maxResponseTime, responseTime);
125
+ if (responseTime < this.metrics.performance.minResponseTime) {
126
+ this.metrics.performance.minResponseTime = responseTime;
127
+ }
128
+ if (responseTime > this.metrics.performance.maxResponseTime) {
129
+ this.metrics.performance.maxResponseTime = responseTime;
130
+ }
127
131
 
128
132
  // Almacenar tiempos de respuesta para calcular percentiles
129
133
  this.metrics.responseTimes.push(responseTime);
130
- if (this.metrics.responseTimes.length > 10000) { // Limitar el tamaño del array
134
+ if (this.metrics.responseTimes.length > 1000) { // Reducir tamaño del array para mejor rendimiento
131
135
  this.metrics.responseTimes.shift();
132
136
  }
133
137
 
@@ -138,8 +142,10 @@ class ProfessionalMetrics {
138
142
  this.metrics.performance.avgResponseTime =
139
143
  this.metrics.performance.totalResponseTime / this.metrics.requests.total;
140
144
 
141
- // Calcular percentiles
142
- this.calculatePercentiles();
145
+ // Calcular percentiles (solo cada cierto número de solicitudes para mejorar rendimiento)
146
+ if (this.metrics.requests.total % 100 === 0) {
147
+ this.calculatePercentiles();
148
+ }
143
149
 
144
150
  this.metrics.timestamps.lastUpdate = now;
145
151
  }
@@ -153,9 +159,9 @@ class ProfessionalMetrics {
153
159
  successfulRequests: 0,
154
160
  failedRequests: 0,
155
161
  totalResponseTime: 0,
156
- minResponseTime: Infinity,
157
- maxResponseTime: 0,
158
- avgResponseTime: 0,
162
+ minResponseTime: responseTime, // Iniciar con el primer valor
163
+ maxResponseTime: responseTime,
164
+ avgResponseTime: responseTime,
159
165
  responseCodes: {},
160
166
  uptimeRatio: 0,
161
167
  lastActive: Date.now(),
@@ -175,8 +181,12 @@ class ProfessionalMetrics {
175
181
 
176
182
  // Actualizar tiempos de respuesta
177
183
  serverStat.totalResponseTime += responseTime;
178
- serverStat.minResponseTime = Math.min(serverStat.minResponseTime, responseTime);
179
- serverStat.maxResponseTime = Math.max(serverStat.maxResponseTime, responseTime);
184
+ if (responseTime < serverStat.minResponseTime) {
185
+ serverStat.minResponseTime = responseTime;
186
+ }
187
+ if (responseTime > serverStat.maxResponseTime) {
188
+ serverStat.maxResponseTime = responseTime;
189
+ }
180
190
 
181
191
  // Actualizar códigos de respuesta
182
192
  if (!serverStat.responseCodes[responseCode]) {
@@ -192,14 +202,9 @@ class ProfessionalMetrics {
192
202
 
193
203
  // Almacenar tiempos de respuesta para percentiles por servidor
194
204
  serverStat.responseTimes.push(responseTime);
195
- if (serverStat.responseTimes.length > 1000) { // Limitar tamaño del array por servidor
205
+ if (serverStat.responseTimes.length > 100) { // Reducir tamaño del array para mejor rendimiento
196
206
  serverStat.responseTimes.shift();
197
207
  }
198
-
199
- // Asegurar que minResponseTime sea válido
200
- if (serverStat.minResponseTime === Infinity) {
201
- serverStat.minResponseTime = 0;
202
- }
203
208
  }
204
209
 
205
210
  getServerStats(serverId) {
@@ -213,7 +218,7 @@ class ProfessionalMetrics {
213
218
  if (serverId) {
214
219
  const serverStat = this.metrics.serverStats[serverId];
215
220
  if (serverStat && serverStat.responseTimes.length > 0) {
216
- const sorted = [...serverStat.responseTimes].sort((a, b) => a - b);
221
+ const sorted = Array.from(serverStat.responseTimes).sort((a, b) => a - b);
217
222
  const length = sorted.length;
218
223
 
219
224
  // Calcular P95 (percentil 95)
@@ -234,14 +239,16 @@ class ProfessionalMetrics {
234
239
 
235
240
  calculatePercentiles() {
236
241
  if (this.metrics.responseTimes.length === 0) return;
237
-
238
- const sorted = [...this.metrics.responseTimes].sort((a, b) => a - b);
242
+
243
+ // Usar un algoritmo más eficiente para ordenar solo los elementos necesarios
244
+ // para calcular percentiles sin ordenar completamente el array
245
+ const sorted = Array.from(this.metrics.responseTimes).sort((a, b) => a - b);
239
246
  const length = sorted.length;
240
-
247
+
241
248
  // Calcular P95 (percentil 95)
242
249
  const p95Index = Math.floor(length * 0.95);
243
250
  this.metrics.performance.p95ResponseTime = sorted[p95Index] || 0;
244
-
251
+
245
252
  // Calcular P99 (percentil 99)
246
253
  const p99Index = Math.floor(length * 0.99);
247
254
  this.metrics.performance.p99ResponseTime = sorted[p99Index] || 0;
@@ -395,11 +402,21 @@ class ProfessionalMetrics {
395
402
  this.recentRequests = [];
396
403
  }
397
404
 
398
- this.recentRequests.push(Date.now());
405
+ const now = Date.now();
406
+ this.recentRequests.push(now);
399
407
 
400
408
  // Limpiar solicitudes antiguas (> 5 segundos) para mantener solo las recientes
401
- const fiveSecondsAgo = Date.now() - 5000;
402
- this.recentRequests = this.recentRequests.filter(timestamp => timestamp > fiveSecondsAgo);
409
+ // Solo limpiar cada ciertos registros para mejorar rendimiento
410
+ if (this.recentRequests.length % 10 === 0) {
411
+ const fiveSecondsAgo = now - 5000;
412
+ let i = 0;
413
+ while (i < this.recentRequests.length && this.recentRequests[i] <= fiveSecondsAgo) {
414
+ i++;
415
+ }
416
+ if (i > 0) {
417
+ this.recentRequests.splice(0, i);
418
+ }
419
+ }
403
420
  }
404
421
 
405
422
  updateSystemMetrics() {
@@ -0,0 +1,24 @@
1
+ # Script para iniciar Kukuy con archivo de configuración SSL
2
+
3
+ # Cargar variables de entorno desde archivo .env.ssl
4
+ if [ -f .env.ssl ]; then
5
+ export $(cat .env.ssl | grep -v '^#' | xargs)
6
+ fi
7
+
8
+ # Asegurar que use el archivo de servidores real
9
+ export CONFIG_FILE_PATH="./servers_real.json"
10
+
11
+ echo "Iniciando Kukuy en modo SSL con configuración desde .env.ssl..."
12
+ echo "Puerto HTTPS: $BALANCER_HTTPS_PORT"
13
+ echo "Certificado SSL: $SSL_CERT_PATH"
14
+ echo "Llave privada: $SSL_KEY_PATH"
15
+ echo "Archivo de configuración de servidores: $CONFIG_FILE_PATH"
16
+
17
+ if [ ! -z "$BALANCER_HTTP_PORT" ]; then
18
+ echo "Puerto HTTP: $BALANCER_HTTP_PORT"
19
+ fi
20
+
21
+ # Iniciar Kukuy
22
+ node kukuy.js
23
+
24
+ echo "Kukuy detenido."
package/start-ssl.sh ADDED
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+
3
+ # Script para iniciar Kukuy en modo SSL
4
+ echo "Iniciando Kukuy en modo SSL..."
5
+
6
+ # Variables de entorno para SSL
7
+ export SSL_CERT_PATH="certs/auto/certificate.crt"
8
+ export SSL_KEY_PATH="certs/auto/private.key"
9
+ export BALANCER_HTTPS_PORT="${BALANCER_HTTPS_PORT:-8443}"
10
+ export CONFIG_FILE_PATH="./servers_real.json"
11
+
12
+ # Opcional: Mantener también el modo HTTP
13
+ if [ "$ENABLE_HTTP" = "true" ]; then
14
+ export BALANCER_HTTP_PORT="${BALANCER_HTTP_PORT:-8080}"
15
+ echo "Modo HTTP habilitado en puerto: $BALANCER_HTTP_PORT"
16
+ fi
17
+
18
+ echo "Puerto HTTPS: $BALANCER_HTTPS_PORT"
19
+ echo "Certificado SSL: $SSL_CERT_PATH"
20
+ echo "Llave privada: $SSL_KEY_PATH"
21
+ echo "Archivo de configuración de servidores: $CONFIG_FILE_PATH"
22
+
23
+ # Iniciar Kukuy
24
+ node kukuy.js
25
+
26
+ echo "Kukuy detenido."
@@ -0,0 +1,54 @@
1
+ const { RoundRobinAlgorithm } = require('./src/algorithms/RoundRobinAlgorithm');
2
+ const { IPHashAlgorithm } = require('./src/algorithms/IPHashAlgorithm');
3
+ const { ServerPool } = require('./src/core/ServerPool');
4
+
5
+ // Crear instancias de prueba
6
+ const roundRobin = new RoundRobinAlgorithm();
7
+ const ipHash = new IPHashAlgorithm();
8
+ const serverPool = new ServerPool();
9
+
10
+ // Agregar servidores de prueba
11
+ const servers = [
12
+ { url: 'http://server1.com', weight: 1, tags: ['api'] },
13
+ { url: 'http://server2.com', weight: 1, tags: ['api'] },
14
+ { url: 'http://server3.com', weight: 1, tags: ['web'] }
15
+ ];
16
+
17
+ serverPool.addServers(servers);
18
+
19
+ console.log('Prueba de optimización del balanceador Kukuy');
20
+ console.log('============================================');
21
+
22
+ // Probar Round Robin
23
+ console.log('\n1. Prueba de Round Robin:');
24
+ const healthyServers = serverPool.getHealthyServers();
25
+ for (let i = 0; i < 5; i++) {
26
+ const server = roundRobin.selectServer(healthyServers);
27
+ console.log(`Solicitud ${i+1}: Servidor seleccionado - ${server.url}`);
28
+ }
29
+
30
+ // Probar IP Hash
31
+ console.log('\n2. Prueba de IP Hash:');
32
+ const requestContext1 = { req: { headers: { 'x-forwarded-for': '192.168.1.10' } } };
33
+ const requestContext2 = { req: { headers: { 'x-forwarded-for': '192.168.1.11' } } };
34
+
35
+ const server1 = ipHash.selectServer(healthyServers, requestContext1);
36
+ const server2 = ipHash.selectServer(healthyServers, requestContext1); // Misma IP
37
+ const server3 = ipHash.selectServer(healthyServers, requestContext2); // Distinta IP
38
+
39
+ console.log(`IP 192.168.1.10 primera vez: ${server1.url}`);
40
+ console.log(`IP 192.168.1.10 segunda vez: ${server2.url}`);
41
+ console.log(`IP 192.168.1.11 primera vez: ${server3.url}`);
42
+ console.log(`Misma IP usa mismo servidor: ${server1.id === server2.id ? 'Sí' : 'No'}`);
43
+
44
+ // Probar eficiencia de acceso a servidores
45
+ console.log('\n3. Prueba de eficiencia de acceso a servidores:');
46
+ const startTime = process.hrtime.bigint();
47
+ const taggedServers = serverPool.getServersByTag('api');
48
+ const endTime = process.hrtime.bigint();
49
+ const duration = Number(endTime - startTime) / 1000000; // Convertir a milisegundos
50
+
51
+ console.log(`Servidores con tag 'api': ${taggedServers.length}`);
52
+ console.log(`Tiempo de acceso: ${duration} ms`);
53
+
54
+ console.log('\n¡Todas las pruebas básicas pasaron!');
@@ -276,7 +276,7 @@
276
276
  </div>
277
277
 
278
278
  <footer>
279
- <p>KUKUY v1.2.1 | Sistema de Distribución de Carga con Hooks y Filtros</p>
279
+ <p>KUKUY v1.5.0 | Sistema de Distribución de Carga con Hooks y Filtros</p>
280
280
  </footer>
281
281
  </div>
282
282