Arquitectura de Node.js
¿Qué es V8 y por qué es importante?
V8 es el motor JavaScript desarrollado en C++ que convierte el código JavaScript en código máquina. Es utilizado tanto en Chrome como en Node.js, y soporta tanto ECMAScript como WebAssembly.
¿Cómo funciona V8?
Tiene dos fases principales de compilacion:
- Ignition: interprete rapido que convierte el js en bytecode para una ejecucion inicial.
- Turbofan: compilador que transforma el bytecode en codigo maquina de manera eficiente.
Este proceso utiliza compilacion JIT (just-in-time).
Proceso de Compilación
La explicación del proceso de compilación es correcta, pero vamos a especificar mejor las fases:
- Análisis y Parsing
- Genera el AST (Abstract Syntax Tree)
- Analiza la estructura del código
- Ignition (Intérprete)
- Convierte el AST en bytecode
- Permite una ejecución inicial rápida
- Recopila datos de tipos y optimización
- TurboFan (Compilador)
- Optimiza el código basado en los datos recopilados
- Convierte el bytecode en código máquina optimizado
- Puede desoptimizar si las suposiciones son incorrectas
Si alguna suposición del compilador es incorrecta, el motor desoptimiza el código y vuelve a la interpretación.
Ventajas del Bytecode
- Eficiencia en memoria: El bytecode ocupa menos espacio
- Velocidad inicial: Generación rápida para ejecución inmediata
- Base para optimizaciones: Permite a TurboFan realizar optimizaciones más efectivas
Cómo maneja Node.js el IO no bloqueante
En sistemas tradicionales, cuando una aplicación necesita leer un archivo, consultar una base de datos o esperar la respuesta de un servicio externo, el proceso se detiene hasta recibir la respuesta. Esto bloquea el hilo principal, afectando el rendimiento y la capacidad de atender múltiples solicitudes al mismo tiempo.
Node.js resuelve este problema con un modelo asíncrono y no bloqueante, ideal para aplicaciones que manejan un gran volumen de solicitudes concurrentes, como plataformas de pagos en línea o servicios de streaming.
Ejemplo: Pagos en línea
Imagina una aplicación de comercio electrónico donde miles de usuarios realizan pagos al mismo tiempo:
- Socket IO: Cuando un usuario envía una solicitud de pago, Node.js abre una conexión con el servicio bancario sin bloquear el sistema. Mientras espera la respuesta del banco, puede seguir procesando pagos de otros usuarios. Esto es posible gracias a herramientas como
epoll,selectopoll, que monitorean múltiples conexiones y notifican cuando hay nuevos datos disponibles. - File IO y resolución DNS: Una vez confirmado el pago, el sistema genera una factura en un archivo y necesita resolver el dominio del banco para futuras transacciones. Estas tareas pueden ser bloqueantes en otros entornos, pero en Node.js se delegan a un pool de hilos administrado por
libuv. Así, el proceso principal sigue funcionando sin interrupciones.
const fs = require("fs");
const dns = require("dns");
// Simula la conexión con un banco de forma asíncrona
function conectarBanco() {
return new Promise((resolve) => {
setTimeout(() => resolve("Conectado al banco"), 1000);
});
}
// Escribe una factura en un archivo de forma asíncrona
function generarFactura() {
return new Promise((resolve, reject) => {
fs.writeFile("factura.txt", "Factura generada", (err) => {
if (err) reject(err);
else resolve("Factura guardada");
});
});
}
// Resuelve el dominio de un banco de forma asíncrona
function resolverBanco() {
return new Promise((resolve, reject) => {
dns.lookup("banco.com", (err, address) => {
if (err) reject(err);
else resolve(`Banco resuelto en ${address}`);
});
});
}
// Ejecutamos las operaciones sin bloquear el hilo principal
async function procesarPago() {
console.log("Procesando pago...");
const [conexion, factura, dominio] = await Promise.all([
conectarBanco(),
generarFactura(),
resolverBanco(),
]);
console.log(conexion);
console.log(factura);
console.log(dominio);
console.log("Pago completado.");
}
procesarPago();
console.log("Mientras tanto, el sistema sigue funcionando...");
Herramientas clave para manejar IO
- select/poll: Estas técnicas permiten monitorear múltiples conexiones, pero su rendimiento disminuye cuando hay muchas solicitudes simultáneas.
- epoll: Mejora la eficiencia al organizar las conexiones en un árbol balanceado, permitiendo a Node.js manejar miles de usuarios sin degradar el rendimiento.
Cómo funciona epoll en un flujo de pagos:
epoll_create: Se inicia un monitor de eventos para rastrear múltiples conexiones activas.epoll_ctl: Se registran las conexiones abiertas, por ejemplo, solicitudes de pago pendientes.epoll_wait: Se espera a que el banco responda y, en cuanto lo hace, se procesa la respuesta sin afectar otras solicitudes en espera.
const net = require('net');
const server = net.createServer((socket) => {
console.log('📡 Nueva conexión recibida');
socket.on('data', async (data) => {
console.log('📥 Datos recibidos, procesando pago...');
const payment = JSON.parse(data);
// Simula el procesamiento del pago (IO asíncrono)
const result = await processPayment(payment);
console.log('✅ Pago procesado, enviando respuesta');
// Envía la respuesta al cliente
socket.write(JSON.stringify(result));
});
socket.on('close', () => {
console.log('🔌 Conexión cerrada');
});
});
server.listen(3000, () => {
console.log('🚀 Servidor de pagos escuchando en el puerto 3000');
});
// Simulación de una función de procesamiento de pagos
async function processPayment(payment) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ status: 'success', amount: payment.amount });
}, 1000); // Simula una latencia de red
});
}
El Event Loop en Node.js: Cómo Maneja Tareas Asíncronas
El Event Loop es el mecanismo que permite que Node.js maneje múltiples tareas asíncronas sin bloquear el hilo principal.
Fases del Event Loop y su función en aplicaciones reales:
-
Timers (setTimeout, setInterval)
- Se ejecutan funciones programadas con un tiempo específico.
- Ejemplo: En una pasarela de pagos, si un usuario tiene 5 minutos para completar una compra, un
setTimeoutpuede cancelar la transacción si no la finaliza a tiempo.
// Sistema de timeout para pagos
function iniciarPago(paymentId) {
const TIMEOUT = 5 * 60 * 1000; // 5 minutos
setTimeout(() => {
cancelarPagoExpirado(paymentId);
}, TIMEOUT);
setInterval(() => {
verificarEstadoPago(paymentId);
}, 30000); // Verificar cada 30 segundos
} -
Pending Callbacks (Callbacks de I/O diferidos)
- Maneja operaciones de I/O que fueron pospuestas tras completarse en el sistema operativo. Estos callbacks se ejecutan después de que una operación de I/O haya finalizado pero antes de que el Event Loop continúe con otras fases.
- Ejemplo: Un sistema de reservas puede recibir múltiples solicitudes a la vez. Aquí se manejan respuestas de archivos o bases de datos que estaban en espera de ser procesadas.
const fs = require('fs');
// Manejo de callbacks pendientes en sistema de pagos
fs.writeFile('payment.log', paymentData, (err) => {
if (err) {
console.error('Error al escribir el log de pago:', err);
return;
}
notificarProcesamiento(paymentId);
}); -
Idle/Prepare (Interno de Node.js)
- Se usa para optimización interna y rara vez es visible en código.
-
Poll (Manejo de eventos de I/O)
- Procesa operaciones de lectura y escritura de archivos, sockets y bases de datos. Esta fase gestiona la mayor parte de las operaciones de I/O y, si no hay callbacks pendientes, puede quedarse en espera de nuevas operaciones antes de continuar.
- Ejemplo: En un banco digital, cuando el usuario verifica su saldo, el sistema lee datos de la base de datos y los envía al cliente en esta fase.
// Ejemplo de operaciones I/O en fase Poll
async function obtenerDatosPago(paymentId) {
try {
const payment = await db.query('SELECT * FROM payments WHERE id = ?', [paymentId]);
const merchantInfo = await db.query('SELECT * FROM merchants WHERE id = ?', [payment.merchantId]);
return { payment, merchantInfo };
} catch (error) {
console.error('Error al obtener datos de pago:', error);
}
} -
Check (Ejecuta setImmediate)
- Procesa tareas que deben ejecutarse inmediatamente después del Poll, antes de que el Event Loop vuelva a la fase de Timers.
- Ejemplo: Si una API de pagos debe registrar un recibo justo después de una transacción exitosa,
setImmediatepuede ejecutar ese proceso antes de la siguiente operación de I/O.
// Ejecución inmediata después de I/O
db.query('UPDATE payments SET status = ?', ['completed'], (err) => {
if (err) {
console.error('Error al actualizar estado de pago:', err);
return;
}
setImmediate(() => {
notificarCompletado(paymentId);
});
}); -
Close Callbacks (Cierre de conexiones y recursos)
- Se ejecutan cuando una conexión o recurso se cierra.
- Ejemplo: Cuando un usuario cierra sesión en una app bancaria, los sockets de la sesión se cierran aquí para liberar recursos.
Macrotareas y Microtareas en Node.js
En Node.js, la ejecución de código asíncrono se organiza en macrotareas y microtareas, con una jerarquía clara sobre cuál se ejecuta primero. Esto es esencial para el rendimiento y control de tareas en aplicaciones de alto tráfico como servicios de pagos, redes sociales o sistemas de chat en tiempo real.
Macrotareas
Las macrotareas son tareas más grandes y menos frecuentes. Entre ellas encontramos:
setTimeoutysetInterval: Permiten ejecutar código después de un cierto tiempo.setImmediate: Ejecuta código inmediatamente después de la fase Poll del Event Loop.- I/O: Entrada/salida de datos, como leer archivos o hacer solicitudes de red.
Ejemplo: En un sistema de pagos, podrías usar setTimeout para gestionar los retrasos en la transacción. Si un pago se retrasa, setTimeout podría volver a intentar la operación después de un tiempo, es decir, una macrotarea.
// Ejemplo completo de macrotareas en sistema de pagos
function procesarPagoConReintentos(paymentData) {
// setTimeout es una macrotarea
setTimeout(() => {
verificarPago(paymentData);
}, 1000);
// setImmediate es una macrotarea
setImmediate(() => {
logearIntento(paymentData);
});
// I/O es una macrotarea
fs.writeFile('payment.log', JSON.stringify(paymentData), () => {
console.log('Log guardado');
});
}
Microtareas
Las microtareas son tareas pequeñas que deben ejecutarse lo más rápido posible. Entre ellas encontramos:
process.nextTick: Permite ejecutar una función justo después de que Node.js termine de procesar la operación actual.Promise.then: Permite ejecutar código cuando una promesa se resuelve.
Ejemplo: En una aplicación de pago en línea, si la transacción es exitosa, se usa Promise.then para ejecutar una función que confirme la operación y actualice el estado del pago en la base de datos. Esto se maneja como una microtarea para que sea lo primero que se ejecute después de que se resuelva la promesa.
// Ejemplo completo de microtareas en sistema de pagos
async function validarPago(paymentData) {
// Promise.then es una microtarea
const validacion = await validarTarjeta(paymentData)
.then(resultado => {
// Esta microtarea se ejecutará antes que cualquier macrotarea
return procesarResultado(resultado);
});
// process.nextTick es una microtarea
process.nextTick(() => {
logearValidacion(validacion);
});
return validacion;
}
Orden de Ejecución
- Microtareas primero: Antes de pasar a la siguiente macrotarea, se ejecutan todas las microtareas pendientes.
- Ejemplo: Si se resuelve una promesa en medio de una macrotarea (como un retraso en un pago), la microtarea (confirmar el pago) se ejecutará primero.
- Macrotareas después de microtareas: Después de ejecutar todas las microtareas, se pasa a la siguiente macrotarea.
- Ejemplo: Después de confirmar un pago, el
setTimeout(macrotarea) se ejecutará para reintentar si es necesario.
- Ejemplo: Después de confirmar un pago, el
¿Por qué es importante?
Este orden de ejecución optimiza el rendimiento de Node.js, permitiendo que se manejen tareas críticas de manera rápida (microtareas) mientras que las tareas menos urgentes (macrotareas) se ejecutan después. En una plataforma de pagos, esto significa que las respuestas rápidas, como la confirmación de transacciones, se procesan antes que otras tareas menos urgentes, como la notificación al usuario sobre el estado del pago.
Worker Threads en Node.js: Multitarea y Paralelismo para una Plataforma de Pagos
Node.js es conocido por ser monohilo, lo que significa que solo tiene un hilo principal para ejecutar el código. Esto es eficiente para manejar solicitudes de I/O (como consultas a bases de datos o peticiones HTTP), pero puede ser un problema cuando se realizan operaciones intensivas en CPU, como cálculos de transacciones complejas o encriptación de datos. Worker Threads permiten ejecutar tareas pesadas en hilos separados sin bloquear el Event Loop, manteniendo la capacidad de atender múltiples solicitudes concurrentes.
¿Por qué usar Worker Threads?
En una plataforma de pagos, durante el procesamiento de una transacción, pueden ocurrir operaciones intensivas en CPU, como:
- Verificación de la validez de una tarjeta de crédito.
- Cálculos de impuestos y tarifas.
- Procesamiento de datos de cifrado y seguridad.
Si estas operaciones se ejecutaran en el hilo principal, bloquearían el Event Loop y la plataforma no podría manejar más solicitudes, retrasando las respuestas a los usuarios o impidiendo que otros pagos se procesen en paralelo.
Solución: Usar Woker Threads para ejecutar estas tareas en hilos separados, dejando que el hilo principal siga gestionando otras solicitudes como la validación de pagos o la verificación de fondos.
Ejemplo de Uso:
Imaginemos que, en el contexto de una plataforma de pagos, necesitamos realizar un cálculo intensivo para determinar los cargos asociados a una transacción. Este cálculo puede ser realizado en un worker para evitar que el proceso principal se vea afectado.
// paymentProcessor.js - Hilo principal
const { Worker } = require('worker_threads');
class PaymentProcessor {
constructor() {
this.fraudDetectionWorker = new Worker('./fraudDetection.js');
this.encryptionWorker = new Worker('./encryption.js');
}
async procesarPago(paymentData) {
// Análisis de fraude en paralelo
const fraudScore = await this.analizarFraude(paymentData);
// Encriptación de datos sensibles en paralelo
const encryptedData = await this.encriptarDatos(paymentData);
return {
fraudScore,
encryptedData
};
}
analizarFraude(paymentData) {
return new Promise((resolve, reject) => {
this.fraudDetectionWorker.postMessage(paymentData);
this.fraudDetectionWorker.once('message', resolve);
this.fraudDetectionWorker.once('error', reject);
});
}
encriptarDatos(paymentData) {
return new Promise((resolve, reject) => {
this.encryptionWorker.postMessage(paymentData);
this.encryptionWorker.once('message', resolve);
this.encryptionWorker.once('error', reject);
});
}
}
// fraudDetection.js - Worker
const { parentPort } = require('worker_threads');
parentPort.on('message', (paymentData) => {
// Análisis intensivo de fraude
const score = analizarPatronesSospechosos(paymentData);
parentPort.postMessage(score);
});
function analizarPatronesSospechosos(paymentData) {
// Algoritmos complejos de detección de fraude
// ...
return fraudScore;
}
¿Qué hace este código?
- El archivo
paymentProcessor.jscrea un worker que ejecuta el scriptcalculateCharge.js. - El worker realiza el cálculo de cargos (como impuestos y tarifas) basados en los datos que le fueron enviados (monto y tasa impositiva).
- Una vez completado el cálculo, el worker envía el resultado de vuelta al hilo principal, que puede seguir procesando el pago sin ser bloqueado por la operación intensiva.
Funcionamiento Interno:
- Inicialización: Se crea el worker y se le pasan datos como el monto de la transacción y la tasa de impuestos.
- Ejecución: En el worker, se crea una instancia independiente de V8 y un Event Loop, lo que permite ejecutar el cálculo de forma paralela sin interferir con el Event Loop principal.
- Comunicación: El worker utiliza
postMessage()para enviar los datos (el resultado del cálculo) de vuelta al hilo principal.
Puntos Claves:
-
Eventos importantes:
message: Cuando el worker envía los resultados al hilo principal.exit: Indica cuándo el worker ha terminado su trabajo.error: Manejo de errores si algo falla en el worker.
-
Compartición de Datos:
- Usamos la transferencia de mensajes, lo que significa que el worker no tiene acceso directo a la memoria del hilo principal.
- MessageChannel o SharedArrayBuffer podrían ser utilizados para compartir datos entre hilos si fuera necesario.
-
Aislamiento:
Cada worker tiene su propio entorno de ejecución (motor V8 y Event Loop), lo que lo hace independiente del hilo principal. No pueden compartir memoria directamente, pero pueden comunicarse mediante mensajes.
¿Qué son los Child Processes en Node.js?
Node.js es un entorno de ejecución de JavaScript de un solo hilo, lo que puede ser un problema para tareas sincrónicas y de alta carga en la CPU. Para resolver esto, se usa el módulo child_process, que permite crear subprocesos y establecer un canal de comunicación entre el proceso principal y los procesos hijos (Inter Process Communication - IPC).
Este módulo también permite ejecutar comandos del sistema y programas externos, como scripts en otros lenguajes (Python, PHP, etc.).
¿Por qué usar Child Processes?
Aunque Node.js tiene worker_threads para tareas intensivas en CPU, los procesos hijos tienen ventajas específicas:
- Ejecución de programas externos: Permiten interactuar con otros ejecutables.
- Aislamiento mejorado: Cada proceso hijo tiene su propia memoria y V8 independiente.
- Escalabilidad: Distribuyen la carga de trabajo en múltiples procesos, aprovechando los sistemas multi-core.
- Robustez: Un fallo en un proceso hijo no afecta al proceso principal.
Creación de procesos hijos en Node.js
El módulo child_process ofrece cuatro métodos principales:
spawn(): Ejecuta un comando en un nuevo proceso y devuelve flujos de datos.exec(): Ejecuta comandos en una shell y devuelve la salida en un buffer.execFile(): Similar aexec(), pero sin invocar una shell.fork(): Crea un nuevo proceso hijo de Node.js con un canal IPC.
// Ejemplo completo de Child Processes en sistema de pagos
const { spawn, exec, fork } = require('child_process');
// 1. Usando spawn para ejecutar un script Python de análisis
const pythonAnalyzer = spawn('python', ['payment_analyzer.py', JSON.stringify(paymentData)]);
pythonAnalyzer.stdout.on('data', (data) => {
console.log(`Análisis completado: ${data}`);
});
// 2. Usando exec para comandos del sistema
exec('openssl enc -aes-256-cbc -in payment.data -out payment.encrypted', (error, stdout, stderr) => {
if (error) console.error(`Error: ${error}`);
console.log(`Datos encriptados: ${stdout}`);
});
// 3. Usando fork para procesamiento en paralelo
const paymentProcessor = fork('payment_processor.js');
paymentProcessor.send({ type: 'process', data: paymentData });
paymentProcessor.on('message', (result) => {
console.log('Pago procesado:', result);
});
Clustering y PM2 en Node.js: Multitarea y Optimización
Node.js es de un solo hilo y no está diseñado para tareas intensivas en CPU. Sin embargo, se pueden utilizar worker threads para tareas paralelizables y child processes para aislamiento de procesos. Aquí es donde entra en juego clustering, que permite balancear la carga entre múltiples procesos hijos, mejorando el rendimiento y la escalabilidad.
¿Cómo funciona Clustering?
El módulo cluster de Node.js permite crear procesos hijos que comparten el mismo puerto. El proceso principal (master) maneja las conexiones entrantes y las distribuye a los procesos secundarios (workers).
- Los procesos hijos se crean con
child_process.fork(), permitiendo comunicación mediante IPC. - Existen dos estrategias de balanceo de carga:
- Round-robin (por defecto en todas las plataformas excepto Windows): El proceso principal distribuye las conexiones equitativamente entre los trabajadores.
- Delegación de socket: El proceso principal crea el socket y los workers aceptan conexiones directamente, pero esto puede causar desequilibrios.
const cluster = require('cluster');
const express = require('express');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} iniciando`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} murió`);
// Reemplazar worker muerto
cluster.fork();
});
// Monitoreo de workers
cluster.on('fork', (worker) => {
console.log(`Worker ${worker.process.pid} iniciado`);
});
// Manejo de mensajes de workers
Object.values(cluster.workers).forEach(worker => {
worker.on('message', (msg) => {
console.log(`Mensaje de worker ${worker.process.pid}:`, msg);
});
});
} else {
const app = express();
// API de pagos
app.post('/api/payments', async (req, res) => {
try {
const result = await processPayment(req.body);
// Notificar al master
process.send({ type: 'payment_processed', data: result });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log(`Worker ${process.pid} iniciado`);
});
}
Eventos del módulo cluster
El proceso maestro puede escuchar eventos como disconnect, exit, error, y message para manejar fallos y reiniciar procesos si es necesario.
Recuperación ante fallos
Si un worker falla, el maestro puede detectarlo y lanzar uno nuevo automáticamente, evitando que el servidor se caiga por completo.
¿Clustering o Worker Threads?
- Clustering: Adecuado para manejar múltiples solicitudes en un servidor HTTP.
- Worker Threads: Mejor para tareas intensivas en CPU dentro de una misma aplicación sin necesidad de procesos separados.
PM2 y Cluster Mode
PM2 es un gestor de procesos que facilita la ejecución y monitoreo de aplicaciones en modo cluster con el comando:
pm2 start app.js -i max
Esto ejecutará la aplicación en múltiples procesos según el número de CPUs disponibles.
Puntos clave:
- ¿Node.js es de un solo hilo?
- Sí y no. El código JavaScript en sí es de un solo hilo, pero Node.js usa un pool de hilos para ciertas operaciones intensivas en CPU o que no tienen soporte asincrónico en el sistema operativo.
- ¿Node.js usa un pool de hilos para todas las operaciones de I/O?
- No. Solo lo usa para operaciones como resolución de DNS, sistema de archivos, criptografía y compresión (zlib).
- ¿Node.js usa un hilo por conexión?
- No. Node.js maneja múltiples conexiones usando operaciones no bloqueantes en sockets en lugar de crear un nuevo hilo por conexión, como lo haría un servidor tradicional basado en hilos.
- ¿Node.js es rápido en todo?
- Depende. Es excelente para tareas de I/O intensivas, pero no es la mejor opción para tareas que consumen mucha CPU sin optimización adecuada (ej., procesamiento de datos pesados).
- ¿El pool de hilos ayuda a escalar operaciones de red?
- No. Node.js maneja la I/O de red en el hilo principal. Para aprovechar múltiples núcleos, se debe usar clustering (con
clusteropm2).
- No. Node.js maneja la I/O de red en el hilo principal. Para aprovechar múltiples núcleos, se debe usar clustering (con
- ¿Aumentar el tamaño del thread pool mejora la escalabilidad?
- No necesariamente. Si el número de hilos supera la cantidad de núcleos, habrá demasiados cambios de contexto, lo que puede reducir el rendimiento.
Diferencias Clave y Casos de Uso
1. Worker Threads
- Mejor para: Operaciones CPU-intensivas dentro de la misma aplicación
- Casos de uso en pagos:
- Cálculos complejos de fraude
- Encriptación/desencriptación de datos
- Procesamiento de grandes conjuntos de transacciones
- Ventajas:
- Comparte memoria con el proceso principal
- Comunicación eficiente entre hilos
- Ideal para paralelización de cálculos
2. Child Processes
- Mejor para: Ejecutar programas externos o comandos del sistema
- Casos de uso en pagos:
- Integración con sistemas legacy
- Ejecución de scripts en otros lenguajes
- Generación de reportes PDF
- Ventajas:
- Aislamiento completo
- Puede ejecutar cualquier programa
- Mejor para tareas que requieren diferentes lenguajes
3. Clustering
- Mejor para: Escalar aplicaciones HTTP
- Casos de uso en pagos:
- Balanceo de carga de API de pagos
- Alta disponibilidad
- Zero-downtime deployments
- Ventajas:
- Aprovecha múltiples cores
- Balanceo de carga automático
- Recuperación automática ante fallos
Ejemplo Integrado
// Ejemplo combinando las tres aproximaciones
const cluster = require('cluster');
const { Worker } = require('worker_threads');
const { spawn } = require('child_process');
if (cluster.isMaster) {
// Iniciar clusters para HTTP
for (let i = 0; i < require('os').cpus().length; i++) {
cluster.fork();
}
} else {
const app = express();
const fraudDetectionWorker = new Worker('./fraudDetection.js');
app.post('/api/payment', async (req, res) => {
try {
// 1. Worker Thread para detección de fraude
const fraudScore = await new Promise((resolve, reject) => {
fraudDetectionWorker.postMessage(req.body);
fraudDetectionWorker.once('message', resolve);
fraudDetectionWorker.once('error', reject);
});
if (fraudScore > 0.7) {
// 2. Child Process para verificación externa
const pythonVerifier = spawn('python', ['verify_payment.py', JSON.stringify(req.body)]);
pythonVerifier.stdout.on('data', (data) => {
// Procesar resultado de verificación
console.log(`Verificación: ${data}`);
});
}
// 3. Cluster maneja múltiples requests
const result = await processPayment(req.body);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
}
- Maneja operaciones de I/O que fueron pospuestas tras completarse en el sistema operativo. Estos callbacks se ejecutan después de que una operación de I/O haya finalizado pero antes de que el Event Loop continúe con otras fases.
- Ejemplo: Un sistema de reservas puede recibir múltiples solicitudes a la vez. Aquí se manejan respuestas de archivos o bases de datos que estaban en espera de ser procesadas.
Recursos
https://medium.com/@manikmudholkar831995/the-v8-javascript-engine-d1434ca77c96
https://medium.com/@manikmudholkar831995/async-io-in-nodejs-a57fe9c3ccc6
https://medium.com/@manikmudholkar831995/event-loop-in-nodejs-999f6db7eb04
https://medium.com/@manikmudholkar831995/worker-threads-multitasking-in-nodejs-6028cdf35e9d
https://medium.com/@manikmudholkar831995/child-processes-multitasking-in-nodejs-751f9f7a85c8
https://medium.com/@manikmudholkar831995/clustering-and-pm2-multitasking-in-nodejs-c6b10249cfd4
https://medium.com/@manikmudholkar831995/debunking-common-nodejs-misconceptions-7172b41d7afeaa