Esta tradução fornecida pelo StrongLoop / IBM.
Este documento pode estar desatualizado em relação à documentação em Inglês. Para obter as atualizações mais recentes, consulte a documentação em Inglês.Este artigo discute as melhores práticas de desempenho e de confiabilidade para aplicativos Express implementados para produção.
Este tópico se enquadra claramente no mundo de “devops”, abordando o desenvolvimento tradicional e as operações. Assim, as informações são divididas em duas partes:
A seguir serão apresentados alguns itens que podem ser feitos no seu código para melhorar o desempenho dos aplicativos:
A compactação Gzip pode diminuir bastante o tamanho do corpo de resposta e assim aumentar a velocidade de um aplicativo da web. Use o middleware compression para fazer a compactação gzip no seu aplicativo do Express. Por exemplo:
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
Para um website com tráfego intenso na produção, a melhor maneira de colocar a compactação em prática, é implementá-la em um nível de proxy reverso (consulte Use um proxy reverso). Neste caso, não é necessário usar o middleware de compactação. Para obter detalhes sobre a ativação da compactação gzip no Nginx, consulte o Módulo ngx_http_gzip_module na documentação do Nginx.
Funções e métodos síncronos impedem o avanço da execução do processo até que eles retornem. Uma única chamada a uma função síncrona pode retornar em poucos microssegundos ou milissegundos, entretanto, em websites com tráfego intenso, essas chamadas se somam e reduzem o desempenho do aplicativo. Evite o uso delas na produção.
Apesar de o Node e muitos módulos fornecerem versões síncronas e assíncronas de suas funções, sempre use as versões assíncronas na produção. O único momento em que o uso de uma função síncrona pode ser justificado é na primeira inicialização.
Se estiver usando o Node.js 4.0+ ou o io.js 2.1.0+, é possível usar a sinalização --trace-sync-io
da linha de comandos para imprimir um aviso e um rastreio de pilha sempre que o seu aplicativo usar uma API síncrona. Obviamente, não seria desejado usar isto na produção, mas sim antes, para garantir que seu código está pronto para produção. Consulte a Atualização
semanal para o io.js 2.1.0 para obter mais informações.
No desenvolvimento, é possível usar a res.sendFile() para entregar arquivos estáticos. Mas
não use isto na produção, pois esta função precisa ser lida a partir
do sistema de arquivos para cada solicitação de arquivo, e portanto
encontraria latência e afetaria o desempenho geral do aplicativo. Observe que a res.sendFile()
não é implementada com a chamada de sistema sendfile o que a tornaria muito mais eficiente.
Ao invés disso, use o middleware serve-static (ou algo equivalente), que é otimizado para a entrega de arquivos para os aplicativos do Express.
Uma opção ainda melhor é usar um proxy reverso para entregar arquivos estáticos; consulte Use um proxy reverso para obter mais informações.
Em geral, existem duas razões para registrar logs em seu aplicativo: Para depuração e para registro de logs de atividade do aplicativo (essencialmente, todo o resto). Usar
o console.log()
ou o console.err()
para imprimir mensagens de log no
terminal é uma prática comum em desenvolvimento. Mas essas
funções são síncronas quando o destino é um terminal ou um arquivo, portanto elas não são adequadas para produção, a não ser que
a saída seja canalizada para outro programa.
Se estiver registrando logs com o propósito de depuração, então ao invés de usar o console.log()
, use um módulo
especial para depuração como o debug. Este
módulo permite que seja usada a variável de ambiente DEBUG para controlar quais mensagens de depuração são enviadas para o
console.err()
, se houver. Para manter o seu aplicativo puramente assíncrono, você deverá canalizar o
console.err()
para outro programa. Mas nesse ponto, você não fará a depuração na produção, não é?
Se estiver registrando logs de atividade do aplicativo (por
exemplo, rastreamento de tráfico ou chamadas de API), ao invés de
usar o console.log()
, use uma biblioteca de
registro de logs como Winston ou Bunyan. Para
obter uma comparação detalhada dessas duas bibliotecas, consulte a postagem do blog do StrongLoop
Comparando o registro de logs no Node.js usando Winston e Bunyan.
Aplicativos do Node caem ao encontrarem uma exceção não capturada. O não tratamento de exceções e a não tomada das ações apropriadas irão fazer com que o seu aplicativo do Express caia e fique off-line. Se seguir os conselhos em Assegurando que o seu aplicativo reinicie automaticamente abaixo, então seu aplicativo se recuperará de uma queda. Felizmente, aplicativos Express tipicamente possuem um tempo curto de inicialização. Contudo, é desejável evitar quedas em primeiro lugar e, para fazer isso, é necessário tratar exceções adequadamente.
Para garantir que está tratando todas as exceções, use as seguintes técnicas:
Antes de se aprofundar nestes tópicos, você deveria ter um entendimento básico de manipulação de erros do Node/Express: usando retornos de chamada erros-first, e propagação de erros no middleware. O Node usa uma convenção “retorno de chamada erros-first” para retorno de erros de funções assíncronas, onde o primeiro parâmetro para a função de retorno de chamada é o objeto de erro, seguido dos dados de resultado nos parâmetros subsequentes. Para indicar que não ocorreram erros, passe null como o primeiro parâmetro. A função de retorno de chamada deve correspondentemente seguir a convenção de retorno de chamada erros-first para tratar o erro de forma significativa. E no Express, a melhor prática é usar a função next() para propagar erros pela cadeia de middlewares.
Para obter mais informações sobre os fundamentos de manipulação de erros, consulte:
Uma coisa que não deveria fazer é escutar a eventos uncaughtException
, emitidos quando uma exceção
emerge regressando ao loop de eventos. Incluir um listener de eventos para uncaughtException
irá mudar o comportamento
padrão do processo que está encontrando uma exceção; o processo irá continuar a execução apesar da exceção. Essa pode parecer como uma boa maneira de prevenir que o seu
aplicativo caia, mas continuar a execução do aplicativo após uma
exceção não capturada é uma prática perigosa e não é recomendada, porque o estado do processo se torna não confiável e imprevisível.
Adicionalmente, usar o uncaughtException
é oficialmente reconhecido como grosseiro
e existe uma proposta
de removê-lo do núcleo. Portando escutar por um uncaughtException
é simplesmente uma má ideia. É
por isso que recomendamos coisas como múltiplos processos e
supervisores: o processo de queda e reinicialização é frequentemente a
forma mais confiável de se recuperar de um erro.
Também não recomendamos o uso de domínios. Ele geralmente não resolve o problema e é um módulo descontinuado.
Try-catch é uma construção da linguagem JavaScript que pode ser usada para capturar exceções em um código síncrono. Use try-catch, por exemplo, para tratar erros de análise sintática de JSON como mostrado abaixo.
Use uma ferramenta como o JSHint ou o JSLint para ajudá-lo a localizar exceções implícitas como erros de referência em variáveis indefinidas.
Aqui está um exemplo de uso de try-catch para tratar uma potencial exceção causadora de queda de processo. Esta função middleware aceita um parâmetro de campo de consulta chamado “params” que é um objeto JSON.
app.get('/search', (req, res) => {
// Simulating async operation
setImmediate(() => {
const jsonStr = req.query.params
try {
const jsonObj = JSON.parse(jsonStr)
res.send('Success')
} catch (e) {
res.status(400).send('Invalid JSON string')
}
})
})
Entretanto, o try-catch funciona apenas para códigos síncronos. Como a plataforma Node é a princípio assíncrona (particularmente em um ambiente de produção), o try-catch deixará de capturar muitas exceções.
Promessas irão tratar quaisquer exceções (ambas explícitas e implícitas) em blocos de códigos assíncronos que usem
then()
. Apenas inclua .catch(next)
no final da cadeia de promessas. Por exemplo:
app.get('/', (req, res, next) => {
// do some sync stuff
queryDb()
.then((data) => makeCsv(data)) // handle data
.then((csv) => { /* handle csv */ })
.catch(next)
})
app.use((err, req, res, next) => {
// handle error
})
Agora todos os erros assíncronos e síncronos são propagados para o middleware de erros.
Entretanto, existem dois alertas:
const wrap = fn => (...args) => fn(...args).catch(args[2])
app.get('/', wrap(async (req, res, next) => {
const company = await getCompanyById(req.query.id)
const stream = getLogoStreamById(company.id)
stream.on('error', next).pipe(res)
}))
Para obter mais informações sobre o manipulação de erros usando promessas, consulte:
A seguir serão apresentados alguns itens que podem ser feitos no seu ambiente de sistema para melhorar o desempenho dos seus aplicativos:
A variável de ambiente NODE_ENV especifica o ambiente no qual um aplicativo está executando (geralmente, desenvolvimento ou produção). Uma das coisas mais simples que podem ser feitas para melhorar o desempenho é configurar NODE_ENV para “produção”.
Configurando NODE_ENV para “produção” faz com que o Express:
Testes indicam que apenas fazendo isso pode melhorar o desempenho por um fator de três!
Se precisar escrever código específico por ambiente, é possível verificar o valor de NODE_ENV com process.env.NODE_ENV
. Esteja
ciente de que verificar o valor de qualquer variável de ambiente incorre em perda de desempenho, e por isso deve ser feito raramente.
Em desenvolvimento, você tipicamente configura variáveis de ambiente no seu shell interativo, por exemplo, usando o
export
ou o seu arquivo .bash_profile
. Mas
em geral você não deveria fazer isto em um servidor de produção; ao invés disso, use o sistema de inicialização do seu sistema
operacional (systemd ou Upstart). A próxima seção fornece mais detalhes sobre a utilização do seu sistema de inicialização em geral,
mas configurando NODE_ENV é tão importante para o desempenho (e fácil de fazer), que está destacado aqui.
Com o Upstart, use a palavra-chave env
no
seu arquivo de tarefa. Por exemplo:
# /etc/init/env.conf
env NODE_ENV=production
Para obter mais informações, consulte o Introdução, Cookbook e Melhores Práticas para o Upstart.
Com o systemd, use a diretiva Environment
no seu arquivo de unidade. Por exemplo:
# /etc/systemd/system/myservice.service
Environment=NODE_ENV=production
Para obter mais informações, consulte Usando Variáveis de Ambiente em Unidades systemd.
Se estiver usando o StrongLoop Process Manager, é possível também configurar a variável de ambiente ao instalar o StrongLoop PM como um serviço.
Em produção, não é desejado que seu aplicativo fique off-line, nunca. Isto significa que é necessário certificar-se de que ele reinicie tanto se o aplicativo cair quanto se o próprio servidor cair. Apesar de se esperar que nenhum desses eventos ocorram, realisticamente você deve considerar ambas as eventualidades:
Aplicativos do Node caem se encontrarem uma exceção não capturada. A principal coisa que precisa ser feita é assegurar que o seu aplicativo esteja bem testado e trate todas as exceções (consulte tratar exceções adequadamente para obter detalhes). Mas por segurança, posicione um mecanismo para assegurar que se e quando o seu aplicativo cair, ele irá automaticamente reiniciar.
Em desenvolvimento, você iniciou o seu aplicativo de forma simples a partir da linha de comandos com o node server.js
ou
algo similar. Mas fazer isso na produção é uma receita para o desastre. Se o aplicativo cair, ele ficará off-line até ser reiniciado. Para
assegurar que o seu aplicativo reinicie se ele cair, use um gerenciador de processos. Um
gerenciador de processos é um “contêiner” para aplicativos que facilita a implementação, fornece alta disponibilidade, e permite o
gerenciamento do aplicativo em tempo real.
Em adição à reinicialização do seu aplicativo quando cai, um gerenciador de processos pode permitir que você:
Os gerenciador de processos mais populares para o Node são os seguintes:
Para uma comparação recurso por recurso dos três gerenciadores de processos, consulte http://strong-pm.io/compare/.
Usando qualquer um desses gerenciadores de processos será o suficiente para manter seu aplicativo funcionando, mesmo se ele cair de tempos em tempos.
Entretanto, o StrongLoop PM possui vários recursos que são especificamente destinados para a implementação na produção. É possível usá-lo e as ferramentas relacionadas do StrongLoop para:
Como explicado abaixo, ao instalar o StrongLoop PM como um serviço do sistema operacional usando o seu sistema de inicialização, ele irá automaticamente reiniciar quando o sistema reiniciar. Assim, ele irá manter seus processos do aplicativo e clusters ativos para sempre.
A próxima camada de confiabilidade é para assegurar que o seu aplicativo reinicie quando o servidor reiniciar. Os sistemas podem ainda assim cair por uma variedade de razões. Para assegurar que o seu aplicativo reinicie se o servidor cair, use o sistema de inicialização integrado no seu sistema operacional. Os dois principais sistemas de inicialização usados atualmente são o systemd e o Upstart.
Existem duas formas de usar sistemas de inicialização com o seu aplicativo Express:
O Systemd é um sistema Linux e gerenciador de serviço. A maioria das distribuições principais do Linux adotaram o systemd como sistema de inicialização padrão.
Um arquivo de configuração de serviço do systemd é chamado de arquivo de unidade, com um nome de arquivo terminando em .service. Aqui está um exemplo de arquivo de unidade para gerenciar um aplicativo Node diretamente (substitua o texto em negrito com valores para o seu sistema e aplicativo):
[Unit]
Description=Awesome Express App
[Service]
Type=simple
ExecStart=/usr/local/bin/node /projects/myapp/index.js
WorkingDirectory=/projects/myapp
User=nobody
Group=nogroup
# Environment variables:
Environment=NODE_ENV=production
# Allow many incoming connections
LimitNOFILE=infinity
# Allow core dumps for debugging
LimitCORE=infinity
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Restart=always
[Install]
WantedBy=multi-user.target
Para obter mais informações sobre o systemd, consulte a referência do systemd (página do manual).
É possível facilmente instalar o StrongLoop Process Manager como um serviço do systemd. Após fazer isso, quando o servidor reiniciar, ele irá automaticamente reiniciar o StrongLoop PM, que irá então reiniciar todos os aplicativos que está gerenciando.
Para instalar o StrongLoop PM como um serviço do systemd:
$ sudo sl-pm-install --systemd
Em seguida inicie o serviço com:
$ sudo /usr/bin/systemctl start strong-pm
Para obter mais informações, consulte Configurando um host de produção (documentação do StrongLoop).
O Upstart é uma ferramenta de sistema disponível em muitas distribuições Linux para inicialização de tarefas e serviços durante a inicialização do sistema, parando-os durante o encerramento, e supervisionando-os. É possível configurar seu aplicativo Express ou gerenciador de processos como um serviço e em seguida o Upstart irá automaticamente reiniciá-lo quando ele cair.
Um serviço do Upstart é definido em um arquivo de configuração de tarefa
(também chamado de uma “tarefa”) com o nome do arquivo terminando com
.conf
. O seguinte exemplo mostra como criar uma
tarefa chamada “myapp” para um aplicativo chamado “myapp” com o
arquivo principal localizado em /projects/myapp/index.js
.
Crie um arquivo chamado myapp.conf
em
/etc/init/
com o seguinte conteúdo (substitua o
texto em negrito com os valores para o seu sistema e aplicativo):
# When to start the process
start on runlevel [2345]
# When to stop the process
stop on runlevel [016]
# Increase file descriptor limit to be able to handle more requests
limit nofile 50000 50000
# Use production mode
env NODE_ENV=production
# Run as www-data
setuid www-data
setgid www-data
# Run from inside the app dir
chdir /projects/myapp
# The process to start
exec /usr/local/bin/node /projects/myapp/index.js
# Restart the process if it is down
respawn
# Limit restart attempt to 10 times within 10 seconds
respawn limit 10 10
NOTA: Este script requer o Upstart 1.4 ou mais novo, suportado no Ubuntu 12.04-14.10.
Como a tarefa está configurada para executar quando o sistema inicia, seu aplicativo será iniciado juntamente com o sistema operacional, e automaticamente reiniciado se o aplicativo ou o sistema cair.
À parte da reinicialização automática do aplicativo, o Upstart permite que você use estes comandos:
start myapp
– Inicia o aplicativorestart myapp
– Reinicia o aplicativostop myapp
– Para o aplicativoPara obter mais informações sobre o Upstart, consulte a Introdução, Cookbook, e Melhores Práticas para o Upstart.
É possível facilmente instalar o StrongLoop Process Manager como um serviço do Upstart. Após fazer isso, quando o servidor reiniciar, ele irá automaticamente reiniciar o StrongLoop PM, que irá então reiniciar todos os aplicativos que está gerenciando.
Para instalar o StrongLoop PM como um serviço do Upstart 1.4:
$ sudo sl-pm-install
Em seguida execute o serviço com:
$ sudo /sbin/initctl start strong-pm
NOTA: Em sistemas que não suportam o Upstart 1.4, os comandos são ligeiramente diferentes. Consulte Configurando um host de produção (documentação do StrongLoop) para obter mais informações.
Em um sistema com múltiplos núcleos, é possível aumentar o desempenho de um aplicativo Node em muitas vezes ativando um cluster de processos. Um cluster executa múltiplas instâncias do aplicativo, idealmente uma instância em cada núcleo da CPU, assim distribuindo a carga e as tarefas entre as instâncias.
IMPORTANTE: Como as instâncias do aplicativo são executadas em processos separados, elas não compartilham o mesmo espaço de memória. Isto é, os objetos são locais para cada instância do aplicativo. Portanto, não é possível manter o estado no código do aplicativo. Entretanto, é possível usar um armazenamento de dados em memória como o Redis para armazenar dados relativos à sessão e ao estado. Este alerta aplica-se a essencialmente todas as formas de escalonamento horizontal, seja a clusterização com múltiplos processos ou múltiplos servidores físicos.
Em aplicativos clusterizados, processos de trabalho podem cair individualmente sem afetar o restante dos processos. Fora as vantagens de desempenho, o isolamento de falhas é outra razão para executar um cluster de processos de aplicativos. Sempre que processo de trabalho cair, certifique-se de registrar os logs do evento e spawn um novo processo usando cluster.fork().
A clusterização é pode ser feita com o módulo de cluster do Node. Isto permite que um processo principal faça o spawn de processos de trabalho e distribua conexões recebidas entre os trabalhadores. Entretanto, em vez de usar este módulo diretamente, é muito melhor usar uma das muitas ferramentas que fazem isso automaticamente por você; por exemplo o node-pm ou o cluster-service.
Se você implementar seu aplicativo no StrongLoop Process Manager (PM), então é possível tirar vantagem da clusterização sem modificar o código do seu aplicativo.
Quando o StrongLoop Process Manager (PM) executa um aplicativo, ele automaticamente executa-o em um cluster com um número de trabalhadores igual ao número de núcleos de CPU do sistema. É possível manualmente alterar o número de processos de trabalho no cluster usando a ferramenta de linha de comandos slc sem parar o aplicativo.
Por exemplo, assumindo que tenha implementado o seu aplicativo para prod.foo.com e o StrongLoop PM está escutando na porta 8701 (a padrão), em seguida configurar o tamanho do cluster para oito usando o slc:
$ slc ctl -C http://prod.foo.com:8701 set-size my-app 8
Para obter mais informações sobre clusterização com o StrongLoop PM, consulte por Clusterização na documentação do StrongLoop.
Outra estratégia para melhorar o desempenho na produção é armazenar em cache o resultado de solicitações, para que o seu aplicativo não repita a operação para entregar a mesma solicitação repetidamente.
Use um servidor de armazenamento em cache como o Varnish ou o Nginx (consulte também Armazenamento em Cache no Nginx) para melhorar imensamente a velocidade e o desempenho do seu aplicativo.
Não importa o quão otimizado um aplicativo é, uma única instância pode manipular apenas uma quantidade limitada de carga e tráfego. Uma maneira de escalar um aplicativo é executar múltiplas instâncias do mesmo e distribuir o tráfego através de um balanceador de carga. Configurar um balanceador de carga pode melhorar o desempenho e velocidade do aplicativo, e permiti-lo escalar mais do que é possível com uma instância única.
Um balanceador de carga é geralmente um proxy reverso que orquestra o tráfego para e de múltiplas instâncias de aplicativo e servidores. É possível facilmente configurar um balanceador de carga para o seu aplicativo usando o Nginx ou o HAProxy.
Com o balanceamento de carga, você pode ter que garantir que solicitações que estão associadas com um ID de sessão em particular conectam ao processo que as originou. Isto é conhecido como afinidade de sessão, ou sessões pegajosas, e podem ser endereçadas pela sugestão acima para usar um armazenamento de dados como o Redis para os dados da sessão (dependendo do seu aplicativo). Para uma discussão, consulte por Usando múltiplos nós.
O StrongLoop Process Manager é integrado com um Controlador Nginx, tornando mais fácil a configurar configurações de ambientes de produção com múltiplos hosts. Para obter mais informações, consulte por Escalando para servidores múltiplos (documentação do StrongLoop).
Um proxy reverso fica em frente a um aplicativo web e executa operações de suporte nas solicitações, fora o direcionamento de solicitações para o aplicativo. Ele pode lidar com páginas de erro, compactação, armazenamento em cache, entrega de arquivos, e balanceamento de carga entre outras coisas.
Entregar tarefas que não requerem conhecimento do estado do aplicativo para um proxy reverso libera o Express para executar tarefas especializadas de aplicativos. Por esta razão, é recomendado executar o Express atrás de um proxy reverso como o Nginx ou o HAProxy na produção.