Temos uma API implementada usando o ServiceStack, hospedado no IIS. Ao realizar o teste de carga da API, descobrimos que os tempos de resposta são bons, mas que se deterioram rapidamente assim que atingimos cerca de 3.500 usuários simultâneos por servidor. Temos dois servidores e, quando atingimos 7.000 usuários, o tempo médio de resposta fica abaixo de 500 ms para todos os pontos de extremidade. Como as caixas estão atrás de um balanceador de carga, temos 3.500 concorrentes por servidor. No entanto, assim que aumentamos o número total de usuários simultâneos, vemos um aumento significativo nos tempos de resposta. Aumentar os usuários simultâneos para 5.000 por servidor fornece um tempo médio de resposta por terminal de cerca de 7 segundos.
A memória e a CPU nos servidores são bastante baixas, enquanto os tempos de resposta são bons e quando eles se deterioram. No pico de 10.000 usuários simultâneos, a CPU fica em média abaixo de 50% e a RAM fica em torno de 3-4 GB em 16. Isso nos deixa pensando que estamos atingindo algum tipo de limite em algum lugar. A captura de tela abaixo mostra alguns contadores importantes no perfmon durante um teste de carga com um total de 10.000 usuários simultâneos. O contador destacado é solicitações / segundo. À direita da captura de tela, você pode ver as solicitações por segundo gráfico se tornando realmente irregulares. Este é o principal indicador para tempos de resposta lentos. Assim que vemos esse padrão, observamos tempos de resposta lentos no teste de carga.
Como resolvemos esse problema de desempenho? Estamos tentando identificar se esse é um problema de codificação ou de configuração. Existem configurações no web.config ou no IIS que possam explicar esse comportamento? O pool de aplicativos está executando o .NET v4.0 e a versão do IIS é 7.5. A única alteração que fizemos das configurações padrão é atualizar o valor de Comprimento da fila do pool de aplicativos de 1.000 para 5.000. Também adicionamos as seguintes configurações ao arquivo Aspnet.config:
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="5000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="5000" />
</system.web>
Mais detalhes:
O objetivo da API é combinar dados de várias fontes externas e retornar como JSON. Atualmente, ele está usando uma implementação de cache do InMemory para armazenar em cache chamadas externas individuais na camada de dados. A primeira solicitação para um recurso buscará todos os dados necessários e quaisquer solicitações subsequentes para o mesmo recurso obterão resultados do cache. Temos um 'corredor de cache' que é implementado como um processo em segundo plano que atualiza as informações no cache em determinados intervalos definidos. Adicionamos bloqueio ao código que busca dados dos recursos externos. Também implementamos os serviços para buscar os dados das fontes externas de forma assíncrona, de modo que o ponto de extremidade seja tão lento quanto a chamada externa mais lenta (a menos que tenhamos dados no cache, é claro). Isso é feito usando a classe System.Threading.Tasks.Task.Poderíamos estar atingindo uma limitação em termos de número de threads disponíveis para o processo?
fonte
Respostas:
Seguindo @DavidSchwartz e @Matt, isso parece um encadeamento, bloqueia o problema de gerenciamento.
Eu sugiro:
Congele as chamadas externas e o cache gerado para elas e execute o teste de carga com informações externas estáticas apenas para descartar qualquer problema não relacionado ao lado do ambiente do servidor.
Use conjuntos de encadeamentos se não os estiver usando.
Sobre as chamadas externas, você disse: "Também implementamos os serviços para buscar os dados das fontes externas de maneira assíncrona, de modo que o ponto de extremidade seja tão lento quanto a chamada externa mais lenta (a menos que tenhamos dados no cache, é claro). "
As perguntas são: - Você verificou se algum dado do cache está bloqueado durante a chamada externa ou apenas ao gravar o resultado da chamada externa no cache? (muito óbvio, mas devo dizer). - Você bloqueia todo o cache ou partes pequenas dele? (muito óbvio, mas devo dizer). - Mesmo sendo assíncronas, com que frequência as chamadas externas são executadas? Mesmo que não sejam executados com tanta frequência, eles podem ser bloqueados por uma quantidade excessiva de solicitações ao cache das chamadas do usuário enquanto o cache está bloqueado. Esse cenário geralmente mostra a porcentagem fixa de CPU usada porque muitos threads estão aguardando em intervalos fixos e o "bloqueio" também deve ser gerenciado. - Você verificou se tarefas externas significam que o tempo de resposta também aumenta quando o cenário lento chega?
Se o problema persistir, sugiro evitar a classe Task e fazer chamadas externas através do mesmo pool de threads que gerencia as solicitações do usuário. Isso é para evitar o cenário anterior.
fonte