Tutorial do módulo cluster do Node

1. Introdução

O módulo cluster é uma ferramenta incorporada ao Node que permite criar e gerenciar workers (processos filho) para aproveitar melhor o poder de processamento das CPUs multi-core. Com o cluster, você pode executar tarefas em paralelo e aumentar a capacidade de processamento do seu aplicativo.

2. Criação dos Workers

O worker representa um processo filho que foi criado pelo processo mestre, eles são responsáveis por executar tarefas específicas e se comunicar com o processo mestre. Já o processo mestre é o responsável por criar e gerenciar os workers, enquanto eles executam as tarefas.

Para utilizar o módulo cluster, o primeiro passo é verificar se o processo atual é o processo mestre ou é um processo filho. Normalmente, você criará um worker para cada núcleo do processador disponível.

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
} else {
    // ...
}

É no processo mestre que você utiliza o método fork() para criar os workers. O método fork() é uma função do módulo cluster que permite criar um novo processo filho como um worker que estará disponível para executar tarefas em paralelo com o processo mestre.

Os workers compartilham o mesmo arquivo JavaScript do processo mestre, mas possuem sua própria instância do ambiente de execução do Node. É por isso que os workers podem ser usados para processar tarefas de forma paralela e assim melhorar o desempenho e a escalabilidade de sua aplicação.

3. Envio de Mensagens Entre os Workers

A comunicação entre o worker pai e os worker filhos no Node pode ser estabelecida por meio de mensagens assíncronas usando o método worker.send() e o evento 'message'.

1. Envio de Mensagem do Worker Pai Para os Workers Filho

  • O worker pai pode enviar mensagens para os worker filhos usando o método worker.send(message) do objeto worker. A mensagem pode ser de qualquer tipo serializável, como objetos, strings, números, etc.
  • Para enviar mensagens para todos os worker filhos, você pode percorrer todos os workers disponíveis e chamar worker.send(message) para cada um deles.
  • No worker filho, ouvimos o evento 'message' usando o process.on. O método process.on é usado para registrar manipuladores de eventos em processos individuais. Ele permite que você ouça eventos específicos dentro de um único processo. No exemplo abaixo, quando a mensagem é recebida, ela é exibida no console.
if (cluster.isPrimary) {
    for (let i = 0; i < numCPUs; i++) {
        const worker = cluster.fork();
        const message = `Olá worker ${worker.id}!`;
        worker.send(message);
    }
} else {
    process.on('message', (message) => {
        console.log(
            `Worker ${cluster.worker.id} recebeu a mensagem: ${message}`
        );
    });
}

2. Envio de Mensagem dos Worker Filhos Para o Worker Pai

  • Os worker filhos podem enviar mensagens para o worker pai usando o método process.send(message) do objeto process.
  • No worker pai, é necessário ouvir o evento 'message' no objeto cluster para receber as mensagens dos worker filhos. O evento 'message' passa como parâmetros o worker que enviou a mensagem e a mensagem em si.
  • Enviamos uma mensagem do worker filho para o worker pai usando process.send(message). E no worker pai, ouvimos o evento 'message' de cada worker filho usando worker.on('message',...).
  • O método cluster.on é usado para registrar manipuladores de eventos no objeto cluster quando estamos no processo mestre. Ele permite que você ouça eventos específicos relacionados ao gerenciamento dos workers pelo cluster.
  • A diferença principal entre os métodos process.on e cluster.on é que process.on é usado para lidar com eventos em um único processo, enquanto cluster.on é usado no processo mestre para lidar com eventos relacionados à gestão dos workers pelo cluster.

4. Principais Eventos da Classe Worker

Alguns dos eventos que podemos ouvir da classe Worker são:

  1. Evento 'message': É acionado quando um worker recebe uma mensagem do processo mestre ou de outros workers. É a forma de comunicação entre os diferentes processos.
  2. Evento 'error': É acionado quando ocorre um erro no worker. É importante ouvir esse evento para lidar com erros e evitar que o worker pare de funcionar.
  3. Evento 'exit': É acionado quando o worker é encerrado. Pode ser devido a uma finalização normal, a um erro não tratado ou a uma reinicialização. É útil ouvir esse evento para executar ações antes que o worker seja encerrado.
  4. Evento 'online': É acionado quando o worker é iniciado e está pronto para receber tarefas do processo mestre. É útil para verificar se um worker está ativo e pronto para trabalhar.

Há outros eventos, como 'disconnect', 'listening' e 'close', que podem ser úteis dependendo do uso.

5. Distribuição de Tarefas Entre os Workers

É possivel criar balanceadores de carga simples para distribuir tarefas entre os worker filhos e com isso processar um grande volume de dados.

Podemos dividir os dados em partes, mesmo que a quantidade de itens seja maior do que o número de clusters filhos disponíveis, e enviar cada parte para processamento em um worker diferente.

Dentro do manipulador de evento, você pode realizar ações com base nos dados recebidos, ou seja, podemos executar qualquer lógica adicional ali.

É importante notar que o evento 'message' é acionado sempre que um worker filho envia uma mensagem para o worker pai. Portanto, se você tiver vários worker filhos, o evento será acionado para cada mensagem enviada por eles.

Lembrando que o código para receber os resultados deve estar no contexto do processo mestre (cluster.isPrimary), pois é nele que os worker filhos são criados e os eventos são ouvidos.

const dataArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

if (cluster.isPrimary) {
    const totalItems = dataArray.length;
    let itemsProcessed = 0;

    for (let i = 0; i < Math.min(cpus, totalItems); i++) {
        const worker = cluster.fork();
        const item = dataArray[i];
        worker.send(item);
        itemsProcessed++;
    }

    cluster.on('message', (worker, message) => {
        if (itemsProcessed < totalItems) {
            const item = dataArray[itemsProcessed];
            worker.send(item);
            itemsProcessed++;
        } else {
            worker.disconnect();
        }
    });
} else {
        process.on('message', (item) => {
            const result = processItem(item);
            process.send(result);
        });
    }
}

6. Principais Métodos e Propriedades do Módulo Cluster

  • cluster.disconnect(): Desconecta todos os workers e encerra o cluster.
  • cluster.fork(): Cria um novo worker filho.
  • cluster.isPrimary: Indica se o processo atual é o processo mestre.
  • cluster.isWorker: Indica se o processo atual é um worker filho.
  • cluster.on(): Registra um manipulador de eventos para um evento específico no cluster.
  • cluster.removeAllListeners(): Remove todos os manipuladores de eventos registrados para um evento específico ou todos os eventos no cluster.
  • cluster.removeListener(): Remove um manipulador de evento para um evento específico no cluster.
  • cluster.settings: Objeto com as configurações atuais do cluster.
  • cluster.worker: Representa o objeto worker atual.
  • cluster.workers: Objeto com todos os worker filhos ativos.

7. Principais Métodos e Propriedades Objeto Worker

  • worker.disconnect(): Desconecta o worker do processo mestre.
  • worker.id: O id exclusivo do worker.
  • worker.isConnected(): Indica se o worker está conectado ao processo mestre.
  • worker.isDead(): Indica se o worker está morto ou terminou sua execução.
  • worker.kill(): Encerra o worker.
  • worker.on(): Registra um manipulador de eventos para um evento específico no worker.
  • worker.process: Uma referência ao objeto process do worker.
  • worker.removeAllListeners(): Remove todos os manipuladores de eventos registrados para um evento específico ou todos os eventos no worker.
  • worker.removeListener(): Remove um manipulador de eventos para um evento específico no worker.
  • worker.send(): Envia uma mensagem para o worker.
  • worker.suicide: Indica se o worker terminou intencionalmente.

8. Principais Métodos e Propriedades do Objeto Process

O objeto process não está diretamente relacionado ao módulo cluster, ele é um objeto global disponível no Node que fornece informações e controle sobre o próprio processo em que o script está sendo executado.

Com o process podemos acessar os argumentos de linha de comando, manipular variáveis de ambiente, eventos relacionados ao processo e métodos para comunicação entre processos.

No exemplo, process.on('message',...) e process.send(...), são funcionalidades utilizadas para estabelecer a comunicação entre o worker pai e os worker filhos.

O método process.on('message',...) permite que o processo ouça mensagens enviadas por outros processos e o método process.send(...) permite que o processo envie mensagens para outros processos.

Utilizamos então o objeto process junto com o cluster para facilitar a comunicação entre os processos pai e filho.

  • process.argv: Array com os argumentos de linha de comando passados para o processo.
  • process.cwd(): Retorna o diretório de trabalho atual do processo.
  • process.env: Objeto que com as variáveis de ambiente do processo.
  • process.exit(): Encerra o processo.
  • process.on(): Registra um manipulador de evento para um evento específico no processo.
  • process.pid: O id do processo atual.
  • process.send(): Envia uma mensagem para um determinado processo.

https://www.lucasmantuan.com.br https://github.com/lucasmantuan https://www.linkedin.com/in/lucasmantuan