node.js, mongodb, redis, na degradação do desempenho do ubuntu em produção, a RAM é livre, CPU 100%

11

Como o título da pergunta sugere, estou tendo dificuldades para descobrir o que pode ser melhorado em meu aplicativo (ou sintonizado no sistema operacional, o Ubuntu) para obter um desempenho aceitável. Mas primeiro vou explicar a arquitetura:

O servidor front-end é uma máquina de 8 núcleos com 8 GB de RAM rodando o Ubuntu 12.04. O aplicativo é escrito inteiramente em javascript e executado em node.js v 0.8.22 (como alguns módulos parecem reclamar em versões mais novas do nó) Eu uso o nginx 1.4 para o tráfego HTTP do proxy da porta 80 e 443 para 8 trabalhadores do nó que são gerenciados e iniciados usando a API do cluster de nós. Eu uso a versão mais recente do socket.io 0.9.14 para lidar com as conexões websocket, nas quais habilitei apenas websockets e xhr-polling como transportes disponíveis. Nesta máquina eu também executo uma instância do Redis (2.2)

Eu armazeno dados persistentes (como usuários e pontuações) em um segundo servidor no mongodb (3.6) com 4gigs RAM e 2 núcleos.

O aplicativo está em produção há alguns meses (está sendo executado em uma única caixa até algumas semanas atrás) e está sendo usado por cerca de 18 mil usuários por dia. Sempre funcionou muito bem, além de uma questão principal: a degradação do desempenho. Com o uso, a quantidade de cpu usada por cada processo aumenta até a estatura do trabalhador (que não atende mais às solicitações). Eu resolvi temporariamente verificar a cpu em uso por cada trabalhador a cada minuto, e reiniciá-lo se atingir 98%. Então o problema aqui é principalmente cpu e não RAM. A RAM não é mais um problema desde que eu atualizei para socket.io 0.9.14 (a versão anterior estava vazando memória) então eu duvido que seja um problema de vazamento de memória, especialmente porque agora é a cpu que cresce rapidamente ( Eu tenho que reiniciar cada trabalhador em torno de 10-12 vezes por dia!). O RAM em uso cresce também para ser honesto, mas muito lentamente, 1 gig a cada 2-3 dias de uso, e o estranho é que ele não é liberado mesmo quando eu reinicio completamente o aplicativo inteiro. Só é liberado se eu reiniciar o servidor! isso eu realmente não consigo entender ...

Eu agora descobri nodefly que é incrível, para que eu possa finalmente ver o que está acontecendo no meu servidor de produção e estou coletando dados desde um par de dias. Se alguém quiser ver os gráficos eu posso te dar acesso, mas basicamente eu posso ver que eu tenho entre 80 e 200 conexões simultâneas! Eu esperava que o node.js lidasse com milhares, não com centenas de solicitações. Também o tempo médio de resposta para o tráfego http flutua entre 500 e 1500 milissegundos, o que eu acho que é realmente muito. Além disso, neste exato momento, com 1300 usuários on-line, esta é a saída de "ss -s":

Total: 5013 (kernel 5533)
TCP:   8047 (estab 4788, closed 3097, orphaned 139, synrecv 0, timewait 3097/0), ports 0

Transport Total     IP        IPv6
*         5533      -         -
RAW       0         0         0
UDP       0         0         0
TCP       4950      4948      2
INET      4950      4948      2
FRAG      0         0         0

que mostra que eu tenho muitas conexões fechadas no tempo. Eu aumentei o máximo de arquivos abertos para 999999, aqui está a saída do ulimit -a:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 63724
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 63724
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Então, achei que o problema poderia estar no tráfego http que, por alguns motivos, satura as portas / soquetes disponíveis (?), mas uma coisa não faz sentido para mim: por que quando eu reinicio os trabalhadores e todos os clientes se reconectam alguns segundos, a carga na cpu do funcionário diminui para 1% e é capaz de atender às solicitações corretamente até que sature após cerca de 1 hora (no horário de pico)?

Sou principalmente um programador de javascript, não um administrador de sistema, por isso não sei quanta carga devo esperar para lidar com meus servidores, mas com certeza ele não está funcionando como deveria. O aplicativo é estável de outra forma e este último problema está me impedindo de enviar as versões móveis do aplicativo que estão prontas, como, obviamente, eles vão trazer mais carga e, eventualmente, quebrar a coisa toda!

Espero que haja algo óbvio que estou fazendo errado, e alguém irá ajudar a identificá-lo ... fique à vontade para me pedir mais informações, e eu sinto muito pela duração da pergunta, mas foi necessário eu acredito ... obrigado antecipadamente!

    
por Franjanko 03.05.2013 / 00:31

3 respostas

10

Após alguns dias de intensas tentativas e erros, fico feliz em poder dizer que entendi onde estava o gargalo, e vou postar aqui para que outras pessoas possam se beneficiar das minhas descobertas.

O problema está nas conexões pub / sub que eu estava usando com o socket.io e, em particular, no RedisStore usado pelo socket.io para lidar com a comunicação entre processos de instâncias de soquete.

Depois de perceber que eu poderia implementar facilmente minha própria versão do pub / sub usando redis, decidi experimentá-lo e removi o redisStore do socket.io, deixando-o com o armazenamento de memória padrão (não preciso para transmitir para todos os clientes conectados, mas apenas entre 2 usuários diferentes conectados possivelmente em diferentes processos)

Inicialmente, declarei apenas 2 conexões de redis globais x process para manipular o pub / sub em cada cliente conectado, e o aplicativo estava usando menos recursos, mas eu ainda estava sendo afetado por um crescimento constante de uso da CPU, portanto não havia muita alteração. Mas então eu decidi tentar criar 2 novas conexões para os redis para cada cliente manipular seus pubs / subs apenas em suas sessões, então feche as conexões uma vez que o usuário desconectasse. Então, depois de um dia de uso na produção, os processadores ainda estavam em 0-5% ... bingo! Nenhum processo é reiniciado, sem bugs, com o desempenho que eu esperava ter. Agora eu posso dizer que node.js rochas e estou feliz por ter escolhido para construir este aplicativo.

Felizmente, o redis foi projetado para lidar com muitas conexões simultâneas (diferentemente pelo mongo) e por padrão é 10k, o que deixa espaço para cerca de 5k usuários simultâneos, em uma única instância de redis, o que é suficiente para mim, mas eu li que ele pode ser empurrado para 64k conexões simultâneas, então essa arquitetura deve ser sólida o suficiente, acredito.

Neste ponto, eu estava pensando em implementar algum tipo de conjunto de conexões para os redis, para otimizá-lo um pouco mais, mas não tenho certeza se isso não fará com que os eventos pub / sub se acumulem nas conexões, a menos que cada um deles é destruído e recriado a cada vez, para limpá-los.

De qualquer forma, obrigado por suas respostas, e ficarei curioso para saber o que você pensa e se você tiver alguma outra sugestão.

Felicidades.

    
por 15.05.2013 / 15:17
2

Você tem algum código-fonte para despejar? Pode ser conexões com o banco de dados não fechado? Processos aguardando conexões HTTP que nunca fecham.

Você pode postar alguns registros?

Faça um ps -ef e certifique-se de que nada ainda esteja em execução. Eu vi processos da web deixarem zumbis que não vão morrer até que você faça um kill -9. Às vezes, o desligamento não funciona ou não funciona totalmente e esses segmentos ou processos armazenam RAM e, às vezes, CPU.

Pode ser um loop infinito em algum lugar no código ou um processo travado mantendo-se ligado a uma conexão de banco de dados.

O que os módulos NPM estão usando? Eles são os últimos?

Você está pegando exceções? Veja: link Consulte: link

Dicas gerais:

link

link

link

link

link

link

    
por 06.05.2013 / 20:51
1

Não é uma resposta em si, já que sua pergunta é mais de uma história do que uma questão de uma resposta.

Apenas para dizer que eu construí um servidor node.js com êxito com o socket.io, que gerencia mais de 1 milhão de conexões persistentes com uma média de carga útil de 700 bytes.

A placa de interface de rede a 1 Gbps estava saturando no início, e eu estava vendo MUITA espera de E / S dos eventos de publicação para todos os clientes.

A remoção do nginx da função de proxy também retornou uma memória preciosa, pois para alcançar um milhão de conexões persistentes com apenas UM servidor, é difícil ajustar parâmetros de configuração, aplicativos e ajustes do sistema operacional. Tenha em mente que isso só é possível com muita RAM (cerca de 1M de conexões websockets consome cerca de 16GB de RAM, com node.js, eu acho que usar o sock.js seria ideal para o consumo de pouca memória, mas por enquanto, o socket.io consome muito).

Este link foi o meu ponto de partida para alcançar esse volume de conexões com o nó. Além de ser um aplicativo Erlang, todo o sistema operacional é praticamente independente de aplicativos e deve ser usado por qualquer pessoa que tenha como objetivo muitas conexões persistentes (websockets ou long-polling).

HTH

    
por 06.05.2013 / 21:16