O que é node.js

Pessoas menos otimistas que eu acreditam que o javascript é a linguagem de programação do futuro. Eu, que sofro de polianisse aguda, acredito que seja a linguagem de programação de hoje. Além de versátil, simples e flexível, o javascript adota, como poucas linguagens, os conceitos de orientação a objetos, tão caros a este velho escovador de bits. Só o suficiente para me deixar apaixonado.

Hoje, uma bela sexta-feira de tempo claro, quente e raro, em Curitiba no fim do inverno de 2013, qualquer pesquisa na internet indicará que o javascript já é uma das linguagens de programação mais utilizadas no mundo. O pesquisador mais cuidadoso perceberá que o javascript suporta grandes sites (KloutVoxer, Microsoft, eBay, Yahoo! e LinkedIn) com milhões de visitas diárias, tanto no front quanto no back end.

Infelizmente, nem tudo são flores. Aprender javascript está cada vez mais difícil. São muitos frameworks, ambientes de desenvolvimento, editores que o pobre do iniciante não sabe por onde começar. Pensando nisso e atendendo aos pedidos de alguns alunos, resolvi escrever uma série de artigos que serão publicados todas as sextas-feiras, aqui no DePijama.

Vamos começar criando um ambiente propício ao aprendizado do javascript. Vamos usar o Windows 8. Por favor, ao atirar pedras utilize a janela da esquerda. Vamos utilizar o node.js, primeiro por que teremos um ambiente de testes em linha de comando mas, principalmente por que, se tudo correr bem, vamos até a criação de um sistema usando o MongoDb. O crème de la crème da tecnologia atual. Coisa de gente ambiciosa. Entretanto, você não precisa sofrer tanto. Pode, por exemplo instalar um ambiente de desenvolvimento baseado no Apache.

Usaremos o Brackets como editor de textos e o Google Chrome como navegador. Novamente, se tiver tempo para atirar pedras, favor usar a janela da esquerda.

Conhecendo o node.js

Segundo próprio site: “o node.js é uma plataforma construída sobre o runtime do Chrome, para facilitar a construção de aplicativos escaláveis, usando um modelo baseado em eventos, não bloqueável que o torna leve e eficiente. Ideal para aplicativos com uso intensivo de dados, ou em tempo real capaz de rodar em sistemas diversos”.

Uau! Realmente simples. Eu, por exemplo, quando li entendi até a palavra runtime. Depois, mosca. E ainda assim só por que sei que tem algo a ver com tempo de execução…

logo do node.js
O node.js uma incrível iniciativa

Trocando em miúdos: trata-se um conjunto de bibliotecas, desenvolvidas em C++, na forma de um aplicativo que permite a execução de código escrito em javascript na linha de comando da sua máquina. Sendo tão simples assim, peço desculpas mas, teremos que olhar os detalhes da explicação do node.js com um pouco mais de cuidado. Afinal, é nos detalhes que moram os bugs.

Runtime do Chrome

Para ser simples o node.js baseia-se no V8, uma máquina virtual, em código aberto, que está sendo exaustivamente desenvolvida pelo Google para o Chrome.  As ambições do V8 incluem: ser mais rápido, independente da plataforma e mais estável que os engines, e máquinas virtuais, que andam por ai, populando nossos navegadores e permitindo o uso de javascript em páginas HTML.

usando o javascript engine do googleO V8 é um bom exemplo de uso de compilador do tipo JIT e, durante algum tempo foi, inegavelmente, o compilador mais rápido entre todos os disponíveis no mercado. Para atingir esta marca, a equipe de desenvolvimento optou pelo modelo JIT, focou na versão ECMA-262 5º edição do javascript e portou seu código para as arquiteturas IA-32, x64 e ARM. Flexibilidade oriunda das diversas arquiteturas e velocidade extraída bit a bit com a arquitetura JIT.

No primeiro ano de desenvolvimento as rotinas responsáveis pela interpretações de expressões regulares o irregexp, foram completamente reescritas, melhorando o desempenho do codigo javascript em execução em 150%.

Já temos várias arquiteturas, JIT e código reescrito. Não acabou.

Ainda em busca de velocidade, a equipe de desenvolvimento resolveu levar um passo adiante os conceitos de implementação de linguagens não tipadas. Criando uma “classe escondida”, um novo padrão de projeto para compilação, que torna mais simples e, consequentemente, mais eficiente a localização de atributos e métodos de objetos, em javascript. Contornando os problemas causados, durante a interpretação do código de uma linguagem que é capaz de acrescentar métodos e atributos à objetos em tempo de execução e cujos tipos de variáveis não são previamente definidos na declaração.

Só para não perder a conta: várias arquiteturas, JIT, código reescrito, novo padrão de projeto. Nada mal!

Os dois toques finais, e não menos importantes, dados pela equipe do V8 para aumentar a velocidade de execução dos programas escritos em javascript estão: na geração de código de máquina de acordo com a necessidade, atendendo a arquitetura JIT, sem o uso de bytecodes intermediários; e na criação de uma estrutura de limpeza de objetos, garbage collector, mais eficiente e quase desnecessária.

Para não deixar passar em branco: odeio bytecode!

Acompanhou? Contabilizando: várias arquiteturas, JIT, código reescrito, novo padrão de projeto, nada de bytecodes e novo garbage collector. É o que temos para hoje. O desenvolvimento do V8 está apenas começando.

Para saber mais sobre a história dos javascript engines, você poderá ler o excelente artigo: The race for speed part 1: The JavaScript engine family tree

Não bloqueável

A web está evoluindo. Originalmente usávamos a web apenas para consumir conteúdo estático. Fotos de gatos, por exemplo. Hoje usamos para convivência, interação social em sites úteis como  o Facebook, onde podemos ver fotos de gatos, publicadas por pessoas que você respeitava, quase em tempo real. Este quase incomoda!

Queremos o tempo real. Queremos conversar, ler e postar, todo o tempo e o tempo todo. Qualquer um com experiência de desenvolvimento web sabe que as requisições HTTP não foram feitas para este cenário. Na verdade, não foram feitas nem para ver as fotos de gatos. O processo de requisição e resposta utilizado pelo protocolo HTTP é lento, arcaico e baseado em textos.

Quando usamos uma linguagem de programação como o PHP, ou o Java, sobre um servidor web como o Apache, com ou sem o Tomcat, cada requisição gera um thread que, por sua vez, provoca a alocação de um determinado espaço de memória. Digamos, apenas para ilustrar, que sejam 1,5Mbytes por conexão. Se assim for, um servidor com 4 Gbytes, será capaz de suportar alguma coisa próximo a 2600 clientes simultâneos. Parece bom? Não é. E olhe que eu nem estou falando dos ciclos e mais ciclos de cpu que são gastos apenas para ficar mudando de thread. Neste esquema, um aumento na demanda só pode ser atendido com alterações na infraestrutura ou na arquitetura do aplicativo. Em língua de gente: caro, muito caro.

Algumas pesquisas científicas parecem indicar que existe um limite prático e que este limite deva ser colocado na conta da falta de visão na hora de escolher a arquitetura utilizada nos sistemas operacionais e nos primeiros servidores web. O problema é tão grave que tem até um nome pomposo: c10k. Ao que parece, depois de 10.000 conexões simultâneas, mesmo grandes investimentos em hardware não trarão benefícios significativos em termos de conexões simultâneas. Ou, em língua de gente novamente: Não adianta coçar o bolso, não vai dar para atender todo mundo.

Na esperança de resolver este problema o node.js adota um único thread, que não pode ser bloqueado por nenhuma requisição de entrada/saída, para tratar todas as requisições que o servidor recebe. A ideia original, o node.js foi criado por Ryan Dahl em 2009, pode ter sido a fomentadora de outras iniciativas na mesma direção: como a tecnologia usada nos servidores web mais modernos como o Nginx e o Thin e em projetos parecidos com o node.js para o Python (Twisted) e para o Ruby (EventMachine). Nada como a cópia e a adoção desenfreada de uma tecnologia para validar sua utilização.

O conceito por trás do nodel.js é simples: toda e qualquer requisição é tratada pelo sistema servidor em um único thread, sem a alocação de memória extra necessária e sem ciclos de cpu desperdiçados na alternância de threads. A leitora mais atenta, lembrou das aulas de arquitetura e está se perguntando: se todas as requisições são tratadas pelo mesmo thread, como é possível rodar programas diferentes para atender necessidades diferentes?

Eita perguntinha difícil sô! Os outros eu não faço ideia de como resolveram este problema mas, o node.js adotou uma arquitetura baseada em eventos.

Baseado em eventos

Formalmente, pode-se dizer que o node.js usa uma arquitetura baseada em eventos de I/O não-blocante. Pronto, lá vamos nós de novo para nos intestinos da tecnologia. Prenda o nariz.

I/O, ou E/S como se usa em português do Brasil, é um termo que se refere a toda operação, feita entre a CPU e qualquer outro dispositivo, por exemplo o disco. No caso da web, este termo é aplicado a tarefas que serão executadas fora do thread principal, por exemplo consultas a bancos de dados. Então, do ponto de vista do node.js, operação de I/O é qualquer operação que precise ser executada fora do thread principal. Aquele único de que falamos acima.

O não-blocante indica que nenhuma operação de I/O irá interromper o fluxo do thread principal. Ou seja, todas as requisições serão executadas, e nenhuma delas será capaz de interromper o processamento das outras enquanto termina de enviar dados.

Antes de receber uma pedrada, me deixe explicar: estou usando este termo, não-blocante, só para concordar com a nomenclatura da página do node.js. Euzinho, tadinho, uso assíncrono. Operação assíncrona de entrada e saída. No fundo quer dizer o mesmo.

O que o node.js faz é criar um laço onde toda e qualquer operação assíncrona de entrada e saída gera um evento. Cada chamada a uma função externa ao thread principal pendura uma função de callback neste laço. Assim que o evento ocorre o callback correspondente é chamado.

Voltando ao setor onde nem tudo são flores. Este modelo têm um calcanhar de Aquiles. Usando um e somente um thread é impossível realizar processamento paralelo. Logo, ele não é, com o perdão da má palavra, eficiente, quando estamos tratando de requisições que exijam uma grande quantidade de processamento. Por exemplo, para comprimir uma imagem. Não se preocupe muito com isso agora, voltaremos a esse tópico lá no fim desta série. E você verá que é possível contornar este problema.