Estamos enfileirando e serializando corretamente?

13

Processamos mensagens por meio de uma variedade de serviços (uma mensagem tocará provavelmente em 9 serviços antes de ser concluída, cada uma executando uma função específica de IO). No momento, temos uma combinação do pior caso (serialização de contrato de dados XML) e do melhor caso (MSMQ na memória) para desempenho.

A natureza da mensagem significa que nossos dados serializados terminam em torno de 12 a 15 kilobytes e processamos cerca de 4 milhões de mensagens por semana. As mensagens persistentes no MSMQ eram muito lentas para nós e, à medida que os dados aumentam, estamos sentindo a pressão dos arquivos mapeados na memória do MSMQ. O servidor tem 16 GB de uso de memória e está crescendo, apenas para filas. O desempenho também sofre quando o uso de memória é alto, pois a máquina começa a trocar. Já estamos fazendo o comportamento de auto-limpeza do MSMQ.

Sinto que há uma parte que estamos fazendo de errado aqui. Tentei usar o RavenDB para manter as mensagens e apenas enfileirar um identificador, mas o desempenho foi muito lento (1000 mensagens por minuto, na melhor das hipóteses). Não tenho certeza se isso é resultado do uso da versão de desenvolvimento ou o quê, mas definitivamente precisamos de uma taxa de transferência maior [1]. O conceito funcionou muito bem na teoria, mas o desempenho não estava à altura da tarefa.

O padrão de uso possui um serviço que atua como roteador, que faz todas as leituras. Os outros serviços anexarão informações com base no gancho de terceiros e retornarão ao roteador. A maioria dos objetos é tocada de 9 a 12 vezes, embora cerca de 10% sejam forçados a circular neste sistema por algum tempo até que terceiros respondam adequadamente. No momento, os serviços são responsáveis ​​por isso e têm comportamentos adequados para dormir, pois utilizamos o campo prioritário da mensagem por esse motivo.

Então, minha pergunta, é o que é uma pilha ideal para a passagem de mensagens entre máquinas discretas, mas com LAN em um ambiente C # / Windows? Normalmente, eu começaria com BinaryFormatter em vez de serialização XML, mas isso é uma toca de coelho se a melhor maneira é descarregar a serialização em um repositório de documentos. Portanto, minha pergunta.

[1]: A natureza de nossos negócios significa que quanto mais cedo processarmos mensagens, mais dinheiro ganharemos. Empiricamente, provamos que processar uma mensagem no final da semana significa que temos menos chances de ganhar esse dinheiro. Embora o desempenho de "1000 por minuto" pareça bastante rápido, precisamos realmente desse número acima de 10k / minuto. Só porque eu estou dando números em mensagens por semana não significa que temos uma semana inteira para processar essas mensagens.

=============== editar:

Informação adicional

Com base nos comentários, adicionarei alguns esclarecimentos:

  • Não sei se a serialização é o nosso gargalo. Comparei o aplicativo e, embora a serialização apareça no gráfico de calor, ela é responsável por apenas 2,5 a 3% da utilização da CPU do serviço.

  • Estou mais preocupado com a permanência de nossas mensagens e com o possível uso indevido do MSMQ. Estamos usando mensagens não transacionais e não persistentes para que possamos manter o desempenho da fila, e eu realmente gostaria de ter pelo menos mensagens persistentes para que elas sobrevivam a uma reinicialização.

  • Adicionar mais RAM é uma medida paliativa. A máquina já passou de 4 GB -> 16 GB de RAM e está ficando cada vez mais difícil desativá-la para continuar adicionando mais.

  • Por causa do padrão de roteamento em estrela do aplicativo, na metade do tempo em que um objeto é populado e empurrado para uma fila, ele não muda. Isso se presta novamente (IMO) para armazená-lo em algum tipo de armazenamento de valor-chave em outro local e simplesmente passar identificadores de mensagem.

  • O padrão de roteamento em estrela é parte integrante do aplicativo e não será alterado. Não podemos centrá-lo no aplicativo, porque cada peça ao longo do caminho opera de forma assíncrona (de maneira polling) e queremos centralizar o comportamento de nova tentativa em um só lugar.

  • A lógica do aplicativo é escrita em C #, os objetos são POCOs imutáveis, o ambiente de implantação de destino é o Windows Server 2012 e podemos suportar máquinas adicionais se um determinado software for suportado apenas no Linux.

  • Meus objetivos são manter a taxa de transferência atual e reduzir o consumo de memória e aumentar a tolerância a falhas com um investimento mínimo de capital.

Bryan Boettcher
fonte
Os comentários foram limpos à medida que os pontos relevantes foram incorporados à pergunta.
ChrisF
Faria sentido resolver o problema mais urgente antes de se preocupar em trocar os subsistemas de enfileiramento (embora isso ainda possa valer a pena). O fato de a memória estar ficando fora de controle sugere que ainda há vazamentos em algum lugar. Que perfil de memória (se houver) foi feito?
Dan Lyons
@ DanLyons: o único crescimento de memória está no MSMQ. Ninguém realmente fala sobre isso, mas parece ser por causa de mensagens não persistentes que são todas mapeadas na memória. Como estamos serializando muitos dados, ele mantém uma quantidade substancial de memória alocada. A memória é (eventualmente) recuperada à medida que as mensagens são consumidas e a limpeza interna do MSMQ é executada.
Bryan Boettcher

Respostas:

1

Aqui estão alguns benchmarks de fila nos quais você pode estar interessado. O MSMQ deve ser capaz de lidar com 10 mil mensagens por segundo. Poderia ser um problema de configuração ou talvez os clientes não estejam acompanhando a leitura da fila? Observe também como o ZeroMQ é incrivelmente rápido nesses benchmarks (cerca de 100 mil mensagens por segundo), ele não oferece uma opção de persistência, mas deve levá-lo ao ponto em que você deseja obter desempenho.

pedregoso
fonte
4

Tivemos uma situação semelhante há vários anos, com um sistema de mensagens na fila (impressões digitais de áudio no nosso caso). Valorizamos fortemente a persistência dos pacotes de dados enfileirados, mas descobrimos que enfileirar tudo no disco e consumir a fila do disco era muito caro.

Se mudarmos para filas baseadas em memória, o desempenho foi excepcional, mas tivemos um grande problema. De vez em quando, os consumidores das filas ficavam indisponíveis por um período de tempo considerável (os elementos consumidor e produtor no nosso caso são conectados via WAN); portanto, a fila do produtor aumentava a um ponto em que se tornava incontrolável e, como no seu caso, uma vez que o consumo de memória era muito alto, o excesso de memória durante a troca provocava um rastreamento completo do sistema.

Nós projetamos uma fila que batizamos deVMQueue (para Fila de memória virtual, um nome muito ruim em retrospectiva). A idéia dessa fila é que, se o processo do consumidor estiver parando, em outras palavras, processando com rapidez suficiente para manter o número de elementos enfileirados abaixo de um determinado nível, então ele terá basicamente o mesmo desempenho de uma memória- fila baseada em No entanto, quando o consumidor fica mais lento ou fica indisponível e a fila do produtor cresce para um determinado tamanho, a fila começará a paginar os elementos automaticamente para o disco de entrada e saída (usandoBinaryFormatterserialização a propósito). Esse processo mantém o uso da memória completamente controlado e o processo de paginação é rápido, ou pelo menos muito mais rápido do que a troca de memória virtual que ocorre durante o carregamento de memória pesada. Depois que o consumidor consegue drenar a fila abaixo do limite, ele continua trabalhando como uma fila baseada em memória pura

Se o sistema travar ou reiniciar, a fila poderá recuperar todos os elementos paginados que foram armazenados no disco, e somente perderá os elementos que ainda estavam na memória antes do travamento. Se você puder perder um número limitado de pacotes durante uma falha ou reinicialização, essa fila pode ser útil.

Se você estiver interessado, posso compartilhar o VMQueuecódigo-fonte da turma para que você possa brincar com ele. A fila aceitará qualquer classe que esteja marcada como serializável. Após a criação da fila, você estabelece o tamanho da página em número de elementos. A interface da classe é praticamente a mesma de uma classe de fila padrão. No entanto, o código é muito antigo (.net 1.1), portanto, nenhuma interface genérica existe.

Sei que sair da comprovada tecnologia MSMQ é uma grande aposta; no entanto, essa fila funciona de maneira confiável há quase 6 anos e nos permitiu sobreviver e nos recuperar de cenários em que a máquina produtora ficou offline por várias semanas! Por favor, me informe se você estiver interessado. :)

sgorozco
fonte
1

O sistema HP ProLiant ML350G5 obtém 82 mil transações por minuto - ou seja, possui mais de 8x a taxa de transferência de "10 mil / minuto" mencionada.

Desempenho: 82.774 tpmC

Além disso, para ser sincero, eu teria usado apenas 64 ou 128 GB de RAM - a RAM é barata. Greenspun aponta a diferença entre "jogar RAM nele" e "fazer com que um cara educado pelo MIT o otimize", e a RAM vence.

Ele terminou com uma máquina SQL Server equipada com 64 GB de RAM e um punhado de máquinas front-end executando páginas ASP.NET ... O site swaptree.com lida com sua associação atual de mais de 400.000 usuários (crescendo rapidamente) sem dificuldade...

A observação "a máquina já atingiu 16 GB de RAM" está longe de ser suficiente, com um artigo apontando um servidor que estava lidando com 400 mil usuários em 64 GB de RAM.

Marcel Popescu
fonte