Ontem um amigo perguntou como implantar o projeto expresso. Por isso compilei este artigo, que fala principalmente sobre como implantar um programa servidor desenvolvido baseado em nodejs para referência de amigos necessitados.
O artigo contém várias partes:
(processo) é o computador alocação do sistema operacional e a unidade básica de agendamento de tarefas . Abra o gerenciador de tarefas e você verá que na verdade existem muitos programas em execução em segundo plano no computador, e cada programa é um processo.
Os navegadores modernos possuem basicamente uma arquitetura multiprocessos. Tomando o navegador Chrome como exemplo, abra "Mais Ferramentas" - "Gerenciador de Tarefas" e você poderá ver as informações do processo do navegador atual. Além disso, existem processos de rede, processos de GPU, etc.
A arquitetura multiprocesso garante uma operação mais estável do aplicativo. Tomando o navegador como exemplo, se todos os programas forem executados em um processo, se houver uma falha de rede ou erro de renderização de página, isso fará com que todo o navegador trave. Através da arquitetura multiprocessos, mesmo que o processo de rede falhe, isso não afetará a exibição das páginas existentes e, na pior das hipóteses, ficará temporariamente impossibilitado de acessar a rede.
Uma thread é a menor unidade que o sistema operacional pode realizar para escalonamento computacional . Ele está incluído no processo e é a unidade operacional real do processo. Por exemplo, um programa é como uma empresa com vários departamentos, que são processos, a cooperação de cada departamento permite que a empresa funcione normalmente, e os threads são os funcionários, as pessoas que fazem o trabalho específico.
Todos nós sabemos que JavaScript é uma linguagem de thread único. Esse design ocorre porque no início JS era usado principalmente para escrever scripts e era responsável por realizar os efeitos interativos da página. Se for projetado como uma linguagem multithread, em primeiro lugar, não é necessário e, em segundo lugar, vários threads operam em conjunto em um nó DOM, então qual conselho o navegador deve ouvir? É claro que, com o desenvolvimento da tecnologia, JS agora também oferece suporte a multithreading, mas é usado apenas para lidar com alguma lógica não relacionada às operações DOM.
Threads únicos e processos únicos trazem um problema sério. Quando o thread principal de um programa node.js em execução for desligado, o processo também será interrompido e todo o aplicativo também será interrompido. Além disso, a maioria dos computadores modernos possui CPUs multi-core, com quatro núcleos e oito threads, e oito núcleos e dezesseis threads, que são dispositivos muito comuns. Como um programa de processo único, o node.js desperdiça o desempenho de CPUs multi-core.
Em resposta a esta situação, precisamos de um modelo multiprocesso adequado para transformar um programa node.js de processo único em uma arquitetura multiprocesso.
Existem duas soluções comuns para implementar arquitetura multiprocessos em Node.js, ambas usando módulos nativos, ou seja, o módulo child_process
e o módulo cluster
.
child_process
é um módulo integrado do node.js. Você pode adivinhar pelo nome que ele é responsável por coisas relacionadas aos processos filhos.
Não entraremos em detalhes sobre o uso específico deste módulo. Na verdade, ele possui apenas cerca de seis ou sete métodos, que ainda são muito fáceis de entender. Usamos um dos métodos fork
para demonstrar como implementar vários processos e comunicação entre vários processos.
Vejamos primeiro a estrutura de diretórios do caso de demonstração preparado:
Usamos o módulo http
para criar um servidor http. Quando uma solicitação /sum
chega, um processo filho será criado por meio do módulo child_process
e o processo filho será notificado para executar a lógica de cálculo. também deve ouvir as mensagens enviadas pelo processo filho:
//child_process.js const http = requer('http') const {fork} = require('child_process') servidor const = http.createServer((req, res) => { if (req.url == '/soma') { // O método fork recebe um caminho de módulo, então inicia um processo filho e executa o módulo no processo filho // childProcess representa o processo filho criado let childProcess = fork('./sum.js') //Envia uma mensagem para o processo filho childProcess.send('O processo filho começa a calcular') // Monitore as mensagens do processo filho no processo pai childProcess.on('message', (data) => { res.end(dados + '') }) //Ouve o evento de fechamento do processo filho childProcess.on('close', () => { // Se o processo filho sair normalmente ou reportar um erro e desligar, ele irá aqui console.log('child process closes') filhoProcess.kill() }) //Ouça o evento de erro do processo filho childProcess.on('error', () => { console.log('erro de processo filho') filhoProcess.kill() }) } if (req.url == '/olá') { res.end('olá') } // Simule o processo pai para relatar um erro if (req.url == '/error') { throw new Error('Erro no processo pai') res.end('olá') } }) servidor.ouvir(3000, () => { console.log('O servidor está rodando em 3000') })
sum.js
é usado para simular as tarefas a serem executadas pelo processo filho. O processo filho escuta as mensagens enviadas pelo processo pai, processa as tarefas de cálculo e depois envia os resultados ao processo pai:
// sum.js função getSoma() { deixe soma = 0 for (seja i = 0; i < 10000 * 1000 * 100; i++) { soma += 1 } soma de retorno } //process é um objeto global em node.js, representando o processo atual. Aqui está o processo filho. // Escuta as mensagens enviadas pelo processo principal process.on('message', (data) => { console.log('Mensagem do processo principal:', dados) resultado const = getSum() //Envia os resultados do cálculo para o processo pai process.send(result) })
Abra o terminal e execute o comando node 1.child_process
:
Visite o navegador:
A seguir, simule a situação em que o processo filho relata um erro:
// sum.js função getSoma() { // .... } // Após o processo filho ser executado por 5 segundos, o processo de simulação desliga setTimeout(() => { lançar novo erro('relatório de erro') }, 1000 * 5) process.on('mensagem', (dados) => { // ... })
Visite o navegador novamente e observe o console após 5 segundos:
O processo filho morreu e então acessa outro URL: /hello
,
Pode-se observar que o processo pai ainda consegue tratar a solicitação corretamente, indicando que o erro relatado pelo processo filho não afetará a operação do processo pai .
A seguir, simularemos o cenário em que o processo pai relata um erro, comentará o relatório de erro simulado do módulo sum.js
, reiniciará o serviço e acessará /error
com o navegador:
Depois de descobrir que o processo pai travou, todo o programa node.js foi encerrado automaticamente e o serviço entrou em colapso completamente, não deixando espaço para recuperação.
Pode-se observar que não é complicado implementar a arquitetura multiprocessos do node.js por meio fork
de child_process
. A comunicação entre processos ocorre principalmente por meio send
e on
. A partir dessa nomenclatura, também podemos saber que a camada inferior deve ser um modelo de publicação-assinatura.
Mas há um problema sério. Embora o processo filho não afete o processo pai, uma vez que o processo pai cometa um erro e desligue, todos os processos filhos serão "mortos de uma só vez". Portanto, esta solução é adequada para bifurcar algumas operações complexas e demoradas em um subprocesso separado . Para ser mais preciso, esse uso é usado para substituir a implementação de multithreading, não de multiprocessamento.
usa o módulo child_process
para implementar multiprocessos, o que parece ser inútil. Portanto, geralmente é recomendado usar o módulo cluster
para implementar o modelo multiprocesso do node.js.
cluster
significa cluster. Acredito que todos estejam familiarizados com esse termo. Por exemplo, no passado, a empresa tinha apenas uma recepção e, às vezes, estava muito ocupada para receber visitantes a tempo. Agora a empresa alocou quatro recepções. Mesmo que três estejam ocupadas, ainda há uma que pode receber novos visitantes. Clustering significa aproximadamente isso. Para a mesma coisa, é razoavelmente atribuído a diferentes pessoas para fazer isso, de modo a garantir que a coisa possa ser feita da melhor maneira.
O uso do módulo cluster
também é relativamente simples. Se o processo atual for o processo principal, crie um número apropriado de subprocessos com base no número de núcleos da CPU e ouça o evento exit
do subprocesso. Se um subprocesso for encerrado, bifurque novamente o novo subprocesso. -processo. Se não for um processo filho, o negócio real será processado.
const http = requer('http') const cluster = require('cluster') const cpus = require('os').cpus() if (cluster.isMaster) { // Quando o programa é iniciado, ele primeiro vai aqui e cria vários subprocessos de acordo com o número de núcleos da CPU for (let i = 0; i < cpus.length; i++) { //Cria um processo filho cluster.fork() } // Quando qualquer processo filho desliga, o módulo do cluster emitirá o evento 'exit'. Neste ponto, o processo é reiniciado chamando fork novamente. cluster.on('sair', () => { cluster.fork() }) } outro { // O método fork é executado para criar um processo filho, e o módulo será executado novamente. Neste momento, a lógica virá aqui const server = http.createServer((req, res) => {. console.log(processo.pid) res.end('ok') }) servidor.ouvir(3000, () => { console.log('O servidor está rodando em 3000', 'pid: ' + process.pid) }) }
Inicie o serviço:
Como você pode ver, o módulo cluster
criou muitos processos filhos e parece que cada processo filho está executando o mesmo serviço da web.
Deve-se observar que esses processos filhos não estão escutando a mesma porta neste momento. O servidor criado pelo método createServer ainda é responsável pelo monitoramento das portas e encaminha as solicitações para cada processo filho.
Vamos escrever um script de solicitação para solicitar o serviço acima e ver o efeito.
//solicitação.js const http = requer('http') for (seja i = 0; i < 1000; i++) { get('http://localhost:3000') }
O módulo http não só pode criar um servidor http, mas também pode ser usado para enviar solicitações http. Axios suporta ambientes de navegador e servidor. No lado do servidor, o módulo http é usado para enviar solicitações http.
Use node
para executar o arquivo e veja o console original:
Os IDs de processo de diferentes subprocessos que tratam especificamente da solicitação são impressos.
Esta é a arquitetura multiprocessos do nodd.js implementada por meio do módulo cluster
.
É claro que quando implantarmos projetos node.js, não escreveremos e usaremos o módulo cluster
de forma tão seca. Existe uma ferramenta muito útil chamada PM2 , que é uma ferramenta de gerenciamento de processos baseada no módulo cluster. Seu uso básico será apresentado nos capítulos subsequentes.
Até agora, passamos parte do artigo apresentando o conhecimento de multiprocessos em node.js. Na verdade, queremos apenas explicar por que precisamos usar pm2 para gerenciar aplicativos node.js. Devido ao espaço limitado deste artigo e à falta de uma descrição precisa/detalhada, este artigo fornece apenas uma breve introdução. Se esta é a primeira vez que você tem contato com esse conteúdo, talvez você não o entenda muito bem, então não se preocupe, logo teremos um artigo mais detalhado.
Este artigo preparou um exemplo de programa desenvolvido usando express, clique aqui para acessar.
Ele implementa principalmente um serviço de interface Ao acessar /api/users
, mockjs
é usado para simular 10 dados do usuário e retornar uma lista de usuários. Ao mesmo tempo, um cronômetro será acionado para simular uma situação de erro:
const express = require('express') const Mock = require('mockjs') const aplicativo = expresso() app.get("/api/users", (req, res) => { const userList=Mock.mock({ 'userList|10': [{ 'id|+1': 1, 'nome': '@cnome', 'e-mail': '@e-mail' }] }) setTimeout(()=> { throw new Error('Falha no servidor') }, 5.000) res.status(200) res.json(userList) }) app.listen(3000, () => { console.log("Serviço iniciado: 3000") })
Teste localmente e execute o comando no terminal:
node server.js
Abra o navegador e acesse a interface da lista de usuários:
Após cinco segundos, o servidor irá travar:
Podemos resolver esse problema mais tarde, quando usarmos o pm2 para gerenciar aplicativos.
Normalmente, após concluir um projeto vue/react, iremos empacotá-lo primeiro e depois publicá-lo. Na verdade, os projetos front-end precisam ser empacotados principalmente porque o ambiente final de execução do programa é o navegador, e o navegador tem vários problemas de compatibilidade e desempenho, como:
.vue
, .jsx
, .ts
precisam ser compiladosProjetos desenvolvidos usando express.js ou koa.js não possuem
.esses problemas. Além disso, o Node.js adota a especificação modular CommonJS e possui um mecanismo de cache ao mesmo tempo, o módulo só será importado quando for utilizado ; Se você empacotá-lo em um arquivo, essa vantagem será realmente desperdiçada. Portanto, para projetos node.js, não há necessidade de empacotar.
Este artigo usa o sistema CentOS como exemplo para demonstrar
Para facilitar a troca de versões de nós, usamos nvm para gerenciar nós.
Nvm (Node Version Manager) é a ferramenta de gerenciamento de versão do Node.js. Através dele, o nó pode ser alternado arbitrariamente entre múltiplas versões, evitando operações repetidas de download e instalação quando a troca de versões for necessária.
O repositório oficial do Nvm é github.com/nvm-sh/nvm. Como seu script de instalação está armazenado no site githubusercontent
, geralmente fica inacessível. Então criei um novo repositório espelho para ele no gitee, para que eu possa acessar seu script de instalação no gitee.
Baixe o script de instalação através do comando curl
e use bash
para executar o script, que concluirá automaticamente a instalação do nvm:
# curl -o- https://gitee.com/hsyq/nvm/raw/master/install.sh | bash
Quando a instalação for concluída Depois disso, abrimos uma nova janela para usar o nvm:
[root@ecs-221238 ~]# nvm -v0.39.1
pode imprimir o número da versão normalmente, indicando que o nvm foi instalado com sucesso.
Agora você pode usar o nvm para instalar e gerenciar o node.
Ver versões de nós disponíveis:
# nvm ls-remote
Instalar nó:
# nvm install 18.0.0
Ver versões de nós instalados:
[root@ecs-221238 ~]# nvm list -> v18.0.0 padrão -> 18.0.0 (-> v18.0.0) iojs -> N/A (padrão) instável -> N/A (padrão) nó -> estável (-> v18.0.0) (padrão) estável -> 18.0 (-> v18.0.0) (padrão)
Selecione uma versão para usar:
# nvm use 18.0.0
Uma coisa a observar é que ao usar o nvm no Windows, você precisa usar direitos de administrador para executar o comando nvm. No CentOS, eu faço login como usuário root por padrão, então não há problema. Se você encontrar erros desconhecidos ao usá-lo, poderá procurar soluções ou tentar ver se o problema é causado por permissões.
Ao instalar o nó, o npm será instalado automaticamente. Verifique os números de versão do nó e do npm:
[root@ecs-221238 ~]# node -v v18.0.0 [root@ecs-221238 ~]# npm -v
A fonte de imagem npm padrão
em 8.6.0é o endereço oficial:
[root@ecs-221238 ~]# npm config get Registry https://registry.npmjs.org/
Mude para a fonte de espelho Taobao doméstica:
[root@ecs-221238 ~]# npm config set Registry https://registry.npmmirror.com
Neste ponto, o servidor instalou o nó The ambiente e npm estão configurados.
Existem muitas maneiras, seja baixando para o servidor a partir do repositório Github/GitLab/Gitee ou fazendo upload localmente por meio da ferramenta FTP. Os passos são muito simples e não serão demonstrados novamente.
O projeto de demonstração é colocado no diretório /www
:
Geralmente, os servidores em nuvem abrem apenas a porta 22 para login remoto. As portas comumente usadas, como 80 e 443, não estão abertas. Além disso, o projeto expresso que preparamos funciona na porta 3000. Portanto, você precisa primeiro ir ao console do servidor em nuvem, encontrar o grupo de segurança, adicionar algumas regras e abrir as portas 80 e 3000.
Durante a fase de desenvolvimento do, podemos usar nodemon
para monitoramento em tempo real e reinicialização automática para melhorar a eficiência do desenvolvimento. Em um ambiente de produção, você precisa usar o grande assassino: PM2.
primeiro instale o pm2 globalmente:
# npm i -g pm2
Execute o comando pm2 -v
para verificar se a instalação foi bem-sucedida:
[root@ecs-221238 ~]# pm2 -v5.2.0
Mude para o diretório do projeto e instale as dependências primeiro:
cd /www/express-demo npm install
e use o comando pm2
para iniciar o aplicativo.
pm2 iniciar app.js -i max // Ou pm2 start server.js -i 2
O aplicativo de gerenciamento PM2 possui dois modos: fork e cluster. Ao iniciar o aplicativo, usando o parâmetro -i para especificar o número de instâncias, o modo cluster será ativado automaticamente. Neste ponto, os recursos de balanceamento de carga estão disponíveis.
-i: instância, o número de instâncias. Você pode escrever um número específico ou configurá-lo para o máximo.
PM2
verificará automaticamente o número de CPUs disponíveis e iniciará tantos processos quanto possível.
O aplicativo foi iniciado agora. PM2 gerenciará o aplicativo na forma de um processo daemon. Esta tabela mostra algumas informações sobre o aplicativo em execução, como status de execução, uso de CPU, uso de memória, etc.
Acesse a interface em um navegador local:
O modo cluster é um modelo de vários processos e várias instâncias . Quando uma solicitação chega, ela é atribuída a um dos processos para processamento. Assim como o uso do módulo cluster
que vimos antes, devido à tutela do pm2, mesmo que um processo morra, o processo será reiniciado imediatamente.
Retorne ao terminal do servidor e execute o comando pm2 logs
para visualizar os logs pm2:
Pode-se observar que a instância do aplicativo com id 1 desliga e pm2 irá reiniciar a instância imediatamente. Observe que o id aqui é o id da instância do aplicativo, não o id do processo.
Neste ponto, a implantação simples de um projeto expresso está concluída. Ao usar a ferramenta pm2, podemos basicamente garantir que nosso projeto possa ser executado de forma estável e confiável.
Aqui está um resumo de alguns comandos comumente usados da ferramenta pm2 para referência.
# Fork mode pm2 start app.js --name app # Defina o nome do aplicativo para app #Modo cluster# Use balanceamento de carga para iniciar 4 processos pm2 start app.js -i 4 # Irá iniciar 4 processos usando balanceamento de carga, dependendo da CPU disponível pm2 iniciar app.js -i 0 # Equivalente ao efeito do comando acima pm2 start app.js -i max # Expanda o aplicativo com 3 processos adicionais aplicativo em escala pm2 +3 # Expanda ou reduza o aplicativo para 2 processos em escala pm2, aplicativo 2 # Visualize o status do aplicativo # Exiba o status de todos os processos da lista pm2 # Imprime a lista de todos os processos em formato JSON bruto pm2 jlist # Use JSON embelezado para imprimir a lista de todos os processos pm2 prettylist # Exibe todas as informações sobre um processo específico pm2 descreve 0 # Use o painel para monitorar todos os processos pm2 moni #Gerenciamento de log# Exiba todos os logs do aplicativo PM2 em tempo real # Exibir logs de aplicativos em tempo real do aplicativo PM2 Logs # Use o formato json para exibir logs em tempo real, não gere logs antigos, apenas gere logs recém-gerados pm2 logs --json #Gerenciamento de aplicativos# Parar todos os processos pm2 parar todos #Reinicia todos os processos pm2 reinicia todos # Para o processo com o ID especificado pm2 stop 0 # Reinicie o processo com o ID especificado pm2 restart 0 # Exclui processo pm2 com ID 0 delete 0 # Exclua todos os processos pm2 delete all
Você pode tentar cada comando sozinho para ver o efeito.
Aqui está uma demonstração especial do comando monit
, que pode iniciar um painel no terminal para exibir o status de execução do aplicativo em tempo real. Todos os aplicativos gerenciados pelo pm2 podem ser alternados através das setas para cima e para baixo:
PM2 possui funções muito poderosas, muito mais do que os comandos acima. Na implantação de um projeto real, você também pode precisar configurar arquivos de log, modo de observação, variáveis de ambiente, etc. Seria muito tedioso digitar comandos manualmente todas as vezes, então o pm2 fornece arquivos de configuração para gerenciar e implantar aplicativos.
Você pode gerar um arquivo de configuração através do seguinte comando:
[root@ecs-221238 express-demo]# pm2 init simple O arquivo /www/express-demo/ecosystem.config.js gerado
irá gerar um arquivo ecosystem.config.js
:
module.exports = { aplicativos: [{ nome: "aplicativo1", script: "./app.js" }] }
Você também pode criar um arquivo de configuração, como app.config.js
:
const path = require('path') módulo.exportações = { // Um arquivo de configuração pode gerenciar vários aplicativos node.js ao mesmo tempo // apps é uma matriz, cada item é a configuração de um aplicativo apps: [{ //Nome da aplicação: "express-demo", // Script do arquivo de entrada do aplicativo: "./server.js", // Existem dois modos para iniciar o aplicativo: cluster e fork. O padrão é fork. exec_mode: 'cluster', // Número de instâncias de aplicação para criar instâncias: 'max', // Ativa o monitoramento e reinicia automaticamente o aplicativo quando o arquivo é alterado watch: true, //Ignora alterações em alguns arquivos de diretório. // Como o diretório de log está colocado no caminho do projeto, ele deve ser ignorado, caso contrário a aplicação irá gerar logs ao ser iniciada. O PM2 irá reiniciar quando monitorar as alterações. Se reiniciar e gerar logs, ele entrará em um número infinito. loop ignore_watch: [ "node_modules", "registros" ], // Caminho de armazenamento do log de erros err_file: path.resolve(__dirname, 'logs/error.log'), //Imprime o caminho de armazenamento do log out_file: path.resolve(__dirname, 'logs/out.log'), //Defina o formato da data na frente de cada log no arquivo de log log_date_format: "YYYY-MM-DD HH:mm:ss", }] }
Deixe o pm2 usar arquivos de configuração para gerenciar aplicativos do nó:
pm2 start app.config.js
Agora os aplicativos gerenciados pelo pm2 colocarão os logs no diretório do projeto (o padrão está no diretório de instalação do pm2) e poderão monitorar as alterações do arquivo. , reinicie automaticamente o serviço.