Desenvolvimento

5 set, 2013

Node.js processando em paralelo

Publicidade

Infelizmente, o Node.js não trabalha com suporte a multi-threads (algo obtido pelas plataformas Java ou .NET), isso é algo que na opinião de alguns desenvolvedores é considerado como um ponto negativo. Infelizmente isso desperta um certo preconceito em levar a sério esta tecnologia. Mas apesar do Node.js ser single-thread é possível sim, prepará-lo para trabalhar com processamento paralelo. Para isso existe nativamente o módulo Cluster.

Ele basicamente instancia novos processos da aplicação, trabalhando de forma distribuída e compartilhando a mesma porta da rede. O número de processos a serem criados quem determina é você, e é claro que a boa prática é instanciar um total de processos relativo à quantidade de núcleos de CPU do servidor. Por exemplo, se tenho um processador de oito núcleos, então posso instanciar oito processos, criando uma rede de oito clusters.

Para garantir com eficiência o trabalho distribuído dos cluster, é necessário que exista um processo pai (conhecido como cluster master). Ele será o processo responsável por balancear a carga de processamento, distribuindo de forma justa entre os demais processos filhos, que são chamados de cluster slave. Implementar essa técnica no Node.js é muito simples, visto que toda distribuição entre os clusters são executadas de forma abstraída para o desenvolvedor.

Outra vantagem é que os clusters são independentes uns dos outros, ou seja, caso um cluster seja encerramento com problemas, ficando offline, os demais continuarão servindo a aplicação mantendo o sistema no ar. Porém, é necessário gerenciar as instancias e encerramento desses processos manualmente.

Com base nesses conceitos, vamos aplicar na prática a implementação básica de clusters. Como faremos isso? É simples! Criaremos um simples “Hello World” em Node.js e em seguida um gerenciador de clusters, pelo qual irá instanciar nossa aplicação. Crie no mesmo diretório dois arquivos: app.js e clusters.js seguindo o conteúdo dos códigos abaixo:

Código: app.js

[javascript]
var http = require(‘http’);

var server = http.createServer(function(request, response){
response.writeHead(200, {‘Content-Type’: ‘text/html’});
response.write(‘<h1>Olá Node.js</h1>’);
response.end();
});

server.listen(3000, function(){
console.log(‘Servidor Node no ar’);
});
[/javascript]

Código: clusters.js

[javascript]
var cluster = require(‘cluster’)
, os = require(‘os’)
;
if (cluster.isMaster) {
var cpus = os.cpus().length;
for (var i = 0; i <= cpus; i++) {
cluster.fork();
}
cluster.on(‘listening’, function(worker) {
console.log(‘Cluster %d esta conectado.’, worker.process.pid);
});
cluster.on(‘disconnect’, function(worker) {
console.log(‘Cluster %d esta desconectado.’, worker.process.pid);
});
cluster.on(‘exit’, function(worker) {
console.log(‘Cluster %d caiu fora.’, worker.process.pid);
});
} else {
require(‘./app’);
}
[/javascript]

Vamos começar do básico, execute em seu terminal o comando: node app.js. Veja que apenas um processo node foi instanciado, para conferir veja no próprio terminal, surgiu apenas uma única mensagem de “Servidor Node no ar” avisando que o servidor esta no ar.

terminal-imagem1

Pronto isso já mostra que nossa aplicação funciona corretamente, agora vamos prepará-la para rodar vários clusters dessa aplicação. Para isso, pare o servidor atual, pressionando as teclas CTRL + C (no Windows/Linux) ou Command + C (no MacOS) e inicie o servidor com clusters através do comando node clusters.js. Dessa vez nossa aplicação vai rodar de forma distribuída e para comprovar que deu certo, veja no terminal quantas vezes se repetiu a mensagem: “Servidor Node no ar” e também surgirá mensagens avisando que um cluster com um ID de processo esta conectado.

terminal-imagem2

Como tudo isso funcionou? Basicamente carregamos o módulo cluster e primeiro verificamos se ele é o cluster master via função cluster.isMaster. Caso ele seja, rodamos um loop cuja suas iterações é baseada no total de núcleos da CPU, isto ocorre através do trecho var cpus = os.cpus().length. A cada iteração rodamos o cluster.fork(), que na prática, instancia um child process (processo filho) desta aplicação. Quando nasce um novo processo (neste caso um processo-filho), consequentemente ele não cai na condicional: if(cluster.isMaster) e com isso é iniciado o servidor da aplicação via require(‘./app’) para o cluster slave.

Também foram incluídos alguns eventos emitidos pelo cluster master. No código existem apenas os principais eventos:

  • listening: acontece quando um cluster está escutando uma porta do servidor. Neste caso, a nossa aplicação está escutando a porta 4000.
  • disconnect: executa seu callback quando um cluster se desconecta da rede.
  • exit: ocorre quando um processo-filho é fechado no sistema operacional.

Muito pode ser explorado no desenvolvimento de clusters no Node.js. Aqui apenas aplicamos o essencial para manter uma aplicação rodando em paralelo, mas caso tenha a necessidade de implementar mais detalhes que explorem ao máximo os clusters, recomendo que leia a documentação de clusters para ficar por dentro de todos os eventos e funções deste módulo e novidades que possam surgem nas novas versões do Node.js.