No mundo do desenvolvimento web, a necessidade de processar grandes volumes de dados é cada vez mais comum. A transcodificação de vídeo, por exemplo, é uma tarefa intensiva que pode se beneficiar enormemente de uma abordagem distribuída. Este artigo explora a construção de um sistema de transcodificação de vídeo distribuído utilizando Node.js, uma plataforma JavaScript de alto desempenho, e Bunnimq, uma biblioteca que facilita a comunicação entre diferentes nós do sistema. Vamos mergulhar em um exemplo prático que demonstra como essa arquitetura pode ser implementada e escalada de forma eficiente.
O Conceito de Sistemas Distribuídos e Brokers
Sistemas distribuídos são conjuntos de computadores que trabalham juntos para realizar uma tarefa. A beleza dessa abordagem reside na capacidade de dividir o trabalho em partes menores, permitindo que múltiplos nós processem simultaneamente, resultando em um aumento significativo na velocidade e na capacidade de lidar com cargas de trabalho pesadas. O broker, também conhecido como message broker, atua como um intermediário, facilitando a comunicação entre os diferentes nós do sistema. Ele recebe as tarefas, as distribui para os nós disponíveis e garante que o resultado final seja entregue corretamente.
Por que usar um Broker?
A utilização de um broker em sistemas distribuídos oferece várias vantagens:
- Escalabilidade: Adicionar mais nós ao sistema se torna simples, pois o broker gerencia a distribuição das tarefas.
- Resiliência: Se um nó falhar, o broker pode redirecionar a tarefa para outro nó disponível, garantindo que o processamento continue sem interrupções.
- Desacoplamento: Os nós não precisam saber uns dos outros, apenas se comunicam com o broker, o que simplifica a arquitetura e a manutenção do sistema.
Em essência, o broker atua como um maestro, orquestrando o trabalho dos diferentes nós e garantindo que tudo funcione em harmonia.
Implementando a Transcodificação Distribuída com Node.js e Bunnimq
Para demonstrar a construção de um sistema de transcodificação de vídeo distribuído, utilizaremos Node.js como a plataforma de desenvolvimento e Bunnimq como o broker de mensagens. O exemplo a seguir apresenta a estrutura básica do sistema, que consiste em três componentes principais: o servidor (broker), o produtor e o consumidor.
Estrutura do Projeto
A estrutura do projeto será organizada da seguinte forma:
ffmpegserver/server.js: O broker Bunnimq, responsável por gerenciar as mensagens.producer.js: O servidor que recebe as solicitações de transcodificação e as envia para a fila.consumer.js: Os nós que recebem as tarefas da fila, transcodificam os vídeos e reportam o resultado..auth: Arquivo com as credenciais de acesso para o broker.
Configurando o Ambiente
Antes de começar, certifique-se de ter o Node.js e o FFmpeg instalados em seu sistema. O FFmpeg é uma ferramenta poderosa para manipulação de vídeo e será utilizada para realizar a transcodificação. Verifique se o FFmpeg está disponível no seu PATH executando o comando ffmpeg -i img.jpg img.png no terminal. Se o comando for executado com sucesso, você está pronto para prosseguir.
Instalando as Dependências
Crie um novo projeto Node.js e instale as dependências necessárias:
npm init -y && npm i bunnimq bunnimq-driver
Este comando inicializa um novo projeto Node.js e instala as bibliotecas bunnimq e bunnimq-driver, que serão utilizadas para implementar o broker e a comunicação com ele, respectivamente.
Implementando o Broker (server.js)
O arquivo server.js implementa o broker Bunnimq, que é responsável por gerenciar as filas de mensagens e distribuir as tarefas para os consumidores. O código a seguir demonstra a configuração básica do broker:
import Bunny from "bunnimq";
import path from "path";
import { fileURLToPath } from "url";
Bunny({
port: 3000,
DEBUG: true,
cwd: path.dirname(fileURLToPath(import.meta.url)), // for .auth file
queue: {
Durable: true,
QueueExpiry: 0,
MessageExpiry: 3600
}
});
Este código configura o broker para escutar na porta 3000, habilita o modo de depuração e define as opções da fila, como durabilidade e tempo de expiração das mensagens.
Implementando o Produtor (producer.js)
O arquivo producer.js implementa o servidor que recebe as solicitações de transcodificação e as envia para a fila. Este servidor atua como a interface entre os clientes e o sistema de transcodificação. O código a seguir demonstra a implementação básica do produtor:
import BunnyMQ from "bunnimq-driver";
import fs from "node:fs/promises";
const bunny = new BunnyMQ({
port: 3000,
host: "localhost",
username: "sk",
password: "mypassword",
});
bunny.queueDeclare(
{
name: "transcode_queue",
config: {
QueueExpiry: 60,
MessageExpiry: 20,
AckExpiry: 10,
Durable: true,
noAck: false,
},
},
(res) => {
console.log("Queue creation:", res);
}
);
async function processVideos() {
const videos = await fs.readdir(
"C:/Users/[path to a folder with videos]/Videos/Capcut/test"
); // usually a storage bucket link
for (const video of videos) {
const job = {
id: Date.now() + Math.random().toString(36).substring(2),
input: `C:/Users/[path to a folder with videos]/Videos/Capcut/test/${video}`,
outputFormat: "webm",
};
bunny.publish("transcode_queue", JSON.stringify(job), (res) => {
console.log(`Job ${job.id} published:`, res ? "ok" : "400");
});
}
}
processVideos();
Este código declara a fila transcode_queue e publica nela as tarefas de transcodificação. Cada tarefa contém o caminho do vídeo de entrada e o formato de saída desejado.
Implementando o Consumidor (consumer.js)
O arquivo consumer.js implementa os nós que recebem as tarefas da fila, transcodificam os vídeos e reportam o resultado. Esses nós são os responsáveis por realizar o trabalho pesado de transcodificação. O código a seguir demonstra a implementação básica do consumidor:
import BunnyMQ from "bunnimq-driver";
import { spawn } from "child_process";
import path from "path";
const bunny = new BunnyMQ({
port: 3000,
host: "localhost",
username: "john",
password: "doees",
});
bunny.consume("transcode_queue", async (msg) => {
console.log("Received message:", msg);
try {
const { input, outputFormat } = JSON.parse(msg);
const absInput = path.resolve(input);
const output = absInput.replace(/\.[^.]+$/, `.${outputFormat}`);
console.log(
`Spawning: ffmpeg -i "${absInput}" -f ${outputFormat} "${output}" -y`
);
await new Promise((resolve, reject) => {
const ffmpeg = spawn(
"ffmpeg",
["-i", absInput, "-f", outputFormat, output, "-y"],
{ shell: true } // helps Windows find ffmpeg.exe
);
ffmpeg.on("error", reject);
ffmpeg.stderr.on("data", (chunk) => {
process.stderr.write(chunk);
});
ffmpeg.on("close", (code, signal) => {
if (code === 0) {
console.log(`Transcoding complete: ${output}`);
return resolve(
bunny.Ack((ok) => console.log("Ack sent:", ok))
);
}
reject(
new Error(
signal ? `Signaled with ${signal}` : `Exited with code ${code}`
)
);
});
});
} catch (error) {
console.error("Error processing message:", error);
if (bunny.Nack) bunny.Nack();
}
});
Este código consome as mensagens da fila transcode_queue, executa o comando ffmpeg para transcodificar o vídeo e envia um ACK (acknowledgement) para o broker para confirmar que a tarefa foi concluída com sucesso.
Executando o Sistema
Para executar o sistema, abra três terminais e execute os seguintes comandos:
- No primeiro terminal, execute
node .\server.jspara iniciar o broker. - No segundo terminal, execute
node .\producer.jspara iniciar o produtor. - No terceiro terminal (e em quantos terminais você quiser para aumentar a capacidade de processamento), execute
node .\consumer.jspara iniciar os consumidores.
Com este setup, o produtor irá ler os vídeos do diretório especificado e enviar as tarefas de transcodificação para a fila. Os consumidores, por sua vez, irão receber as tarefas, transcodificar os vídeos e enviar o resultado para o broker. A beleza deste sistema reside na sua escalabilidade: basta adicionar mais consumidores para aumentar a capacidade de processamento.
Conclusão
A construção de um sistema de transcodificação de vídeo distribuído com Node.js e Bunnimq demonstra o poder e a flexibilidade dos sistemas distribuídos. A capacidade de dividir o trabalho em partes menores e distribuí-las entre múltiplos nós permite lidar com cargas de trabalho pesadas de forma eficiente e escalável. Embora este exemplo utilize Bunnimq, o mesmo padrão pode ser aplicado com outras tecnologias, como RabbitMQ, demonstrando a versatilidade da arquitetura. O futuro da tecnologia aponta para sistemas cada vez mais distribuídos e descentralizados, e a capacidade de construir e gerenciar esses sistemas será uma habilidade fundamental para os desenvolvedores web.