NodeJS: Servidores de alto rendimiento en Javascript

14 junio 2011

Autor: Marcos Reyes

 

Nodejs surge como una forma de sacar valor a la experiencia actual en lenguajes de scripting (en este caso javascript) en el lado servidor. Los perfiles de los desarrolladores Web suelen estar alejados de los problemas típicos que aparecen en los servicios de backend (sobre todo en cuanto a concurrencia y mejores practicas para el acceso a recursos compartidos y/o de entrada salida). Y por otra parte han incrementado la experiencia en programación asíncrona y orientada a eventos.Nodejs simplifica hasta el extremo este tipo de tareas, proponiendo un modelo de ejecución y programación “cerrado” y fácilmente inteligible que potencia la realización de servicios “eficientes” sin necesidad de tener en cuenta la complejidad habitual de este tipo de sistemas. Podría decirse que con Nodejs es difícil hacer las cosas mal.

 

En el lado negativo tenemos muchas funciones anidadas y clousures, así como una programación orientada a eventos que puede dificultar la inteligibilidad del código en algunos casos, pero que no dejan de ser buenas herramientas para definir el comportamiento de un servidor.Por supuesto NodeJs y su motor Javascript (V8) no son la opción más interesante para realizar procesamientos complejos de la información. No es tecnología para multiplicar matrices. Aunque el rendimiento de la V8 y sus mecanismos de optimización (JIT) proporcionan un rendimiento en ejecución más que aceptable para el dominio en el que NodeJs esta centrado: entornos en los que se necesita un intermediario que obtenga y proporcione datos presentes en otros sistemas (servir contenidos, acceder a una base de datos, hacer de proxy a servicios…). También se encuentra especialmente indicado para proporcionar datos en modo streaming sobre una misma conexión cliente. En realidad este es el escenario más común con el que nos encontramos en nuestros backend de servicios.

 

Modelo de ejecución

Desde el punto de vista del programador cada proceso servidor Nodejs cuenta con un único thread para servir todas las peticiones de los clientes (permitiendo N servidores diferentes asociados a dicho proceso). Todo se serializa en este punto, bajo un único bucle de eventos, evitando los problemas de la programación multithread. Ya no tenemos que preocuparnos de proteger memoria compartida o de problemas de carreras, minimizando tanto tiempos de desarrollo como la aparición de errores. Hay que tener en cuenta que servir una nueva petición supone muy poco esfuerzo para el proceso NodeJs, siendo una tarea mucho más ligera que crear un nuevo thread de petición tal y como harían gran parte los servidores multithread al uso.
Conviene remarcar que un fallo en la lógica de servicio de dicho thread (por ejemplo un memory-leak) puede hacer que el proceso NodeJs se “pare” dejando de servir todo tipo de peticiones. NodeJs ayuda a que esto no ocurra (gracias a su modelo de programación y a warnings que nos advierten de posibles problemas), pero conviene ser cuidadoso en este punto.
Respecto a los servidores que pre-crean sus threads de petición en el arranque (P. ej Cherokee) las diferencias no son tan obvias. Si se desea paralelismo a este nivel, deberían lanzarse N procesos -shared nothing- NodeJS junto con algún mecanismo de distribución de carga. Un mecanismo bastante versátil en la era Cloud y que puede considerarse muy interesante frente a un modelo multithread con memoria compartida.

Internamente NodeJs posee un pool de threads autogestionado. Sobre este pool, en general, se lanzan todas las tareas que requieren algún tipo de entrada salida (invocación a servicios, acceso al SO…) o todas aquellas que tareas que se consideren bloqueantes. Es decir NodeJs es monothread para el programador (y para el flujo de servicio principal), pero no lo es en ejecución. Todas las llamadas que impliquen una cierta espera son invocadas de forma asíncrona, proporcionando bien un manejador de callback, bien un patrón de suscripción a eventos. A partir de este punto es el pool de threads interno el que se encarga de la ejecución de la tarea, emitiendo el evento correspondiente cuando esta finalice (o ante cualquier otro supuesto que requiera nuestra intervención). Los bloqueos de espera en el thread principal son por tanto eliminados por construcción, tenemos solo un thread, pero en general siempre esta listo para servir trabajo (se supone que un proceso NodeJs puede servir sin problemas unos 10.000 clientes simultáneos), con una carga mínima en el sistema por cada nueva petición.

Fragmento Servidor

var http = require(’http’),

io = require(’socket.io’),

url = require(’url’);

console.log(’starting websocketserver’);

 

server = http.createServer().listen(8081);

var socket = io.listen(server);

socket.on(’connection‘, function(client){

console.log(’new client:’+client.sessionId);

client.broadcast(’New client connected:’+client.sessionId);

client.on(’message‘, function(msg){

switch(msg.type)

{

case ‘newuser’:

var err = createUser(msg.text, client.sessionId);

if (err==0) client.send(’Already registered’);

else client.send(msg.text+’ Is your new Nick Name’);

break;

default:

var userid = getUser(client.sessionId);

if (userid!=null){

//All but sender

client.broadcast(’<strong>’+userid+’</strong>->’+msg.text);

//Sender only

client.send(’<-’+msg.text);

}

else client.send(’<-YOU MUST STABLISH A NICK-NAME’);

}

});

 

client.on(’disconnect‘, function(){

console.log(’disconected:’+client.sessionId);

deleteUser(client.sessionId);

});

});

 

//(…) +=aux functions)

 

Fragmento CLIENTE

 

<script>

(…)

var socket = new io.Socket(SERVERADDR, SERVERPORT);

var nick=null;

socket.on(’connect’, function(){

var board = document.getElementById(’board’);

board.innerHTML = board.innerHTML + ‘</br>You are connected to chat’;

});

socket.connect();

socket.on(’message’, onMessage);

var board = document.getElementById(’board’);

 

function onMessage(message){

board.innerHTML = board.innerHTML + ‘</br>’ + message.text;

}

 

function sendbroadcastmsg (text){

var msg = new Object(); msg.type=’broadcast’;

msg.text=text; socket.send(msg);

return false;

}

 

function sendnewuser(name){

var msg = new Object();

msg.type=’newuser’;

msg.text=name;

socket.send(msg);

nick = name;

}

 

</script>

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

POST RELACIONADOS