Nous avons une API qui est implémentée à l'aide de ServiceStack qui est hébergé dans IIS. En effectuant des tests de charge de l'API, nous avons découvert que les temps de réponse sont bons mais qu'ils se détériorent rapidement dès que nous atteignons environ 3 500 utilisateurs simultanés par serveur. Nous avons deux serveurs et lorsqu'ils atteignent 7 000 utilisateurs, les temps de réponse moyens sont inférieurs à 500 ms pour tous les points de terminaison. Les boîtes sont derrière un équilibreur de charge, nous avons donc 3 500 concurrents par serveur. Cependant, dès que nous augmentons le nombre total d'utilisateurs simultanés, nous constatons une augmentation significative des temps de réponse. L'augmentation du nombre d'utilisateurs simultanés à 5 000 par serveur nous donne un temps de réponse moyen par point de terminaison d'environ 7 secondes.
La mémoire et le processeur sur les serveurs sont assez faibles, à la fois pendant que les temps de réponse sont bons et après s'être détériorés. Au sommet avec 10 000 utilisateurs simultanés, le processeur est en moyenne un peu moins de 50% et la RAM se situe autour de 3-4 Go sur 16. Cela nous laisse penser que nous atteignons une sorte de limite quelque part. La capture d'écran ci-dessous montre certains compteurs clés de perfmon lors d'un test de charge avec un total de 10 000 utilisateurs simultanés. Le compteur en surbrillance est requêtes / seconde. À droite de la capture d'écran, vous pouvez voir le graphique des requêtes par seconde devenir vraiment erratique. Il s'agit du principal indicateur de temps de réponse lents. Dès que nous voyons ce modèle, nous remarquons des temps de réponse lents dans le test de charge.
Comment résoudre ce problème de performances? Nous essayons d'identifier s'il s'agit d'un problème de codage ou d'un problème de configuration. Existe-t-il des paramètres dans web.config ou IIS qui pourraient expliquer ce comportement? Le pool d'applications exécute .NET v4.0 et la version IIS est 7.5. La seule modification que nous avons apportée par rapport aux paramètres par défaut est de mettre à jour la valeur de longueur de file d'attente du pool d'applications de 1 000 à 5 000. Nous avons également ajouté les paramètres de configuration suivants au fichier Aspnet.config:
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="5000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="5000" />
</system.web>
Plus de détails:
Le but de l'API est de combiner les données de diverses sources externes et de les renvoyer en JSON. Il utilise actuellement une implémentation de cache InMemory pour mettre en cache les appels externes individuels au niveau de la couche de données. La première demande à une ressource récupérera toutes les données requises et toute demande ultérieure pour la même ressource obtiendra les résultats du cache. Nous avons un «exécuteur de cache» qui est implémenté comme un processus d'arrière-plan qui met à jour les informations dans le cache à certains intervalles définis. Nous avons ajouté un verrouillage autour du code qui récupère les données des ressources externes. Nous avons également implémenté les services pour extraire les données des sources externes de manière asynchrone afin que le point de terminaison ne soit aussi lent que l'appel externe le plus lent (sauf si nous avons des données dans le cache bien sûr). Cela se fait à l'aide de la classe System.Threading.Tasks.Task.Pourrions-nous atteindre une limitation en termes de nombre de threads disponibles pour le processus?
la source
Réponses:
Après @DavidSchwartz et @Matt, cela ressemble à un problème de gestion des verrous.
Je suggère:
Geler les appels externes et le cache généré pour eux et exécuter le test de charge avec des informations externes statiques juste pour éliminer tout problème non lié au côté serveur - environnement.
Utilisez des pools de threads si vous ne les utilisez pas.
À propos des appels externes, vous avez dit "Nous avons également implémenté les services pour extraire les données des sources externes de manière asynchrone afin que le point de terminaison ne soit aussi lent que l'appel externe le plus lent (sauf si nous avons des données dans le cache bien sûr). "
Les questions sont les suivantes: - Avez-vous vérifié si des données de cache sont verrouillées pendant l'appel externe ou uniquement lors de l'écriture du résultat de l'appel externe dans le cache? (trop évident mais faut dire). - Verrouillez-vous la totalité du cache ou de petites parties? (trop évident mais faut dire). - Même s'ils sont asynchrones, à quelle fréquence les appels externes sont-ils exécutés? Même s'ils ne s'exécutent pas si souvent, ils peuvent être bloqués par une quantité excessive de demandes au cache provenant des appels des utilisateurs pendant que le cache est verrouillé. Ce scénario montre généralement un pourcentage fixe de CPU utilisé car de nombreux threads attendent à intervalles fixes et le "verrouillage" doit également être géré. - Avez-vous vérifié si les tâches externes signifient que le temps de réponse augmente également lorsque le scénario lent arrive?
Si le problème persiste, je suggère d'éviter la classe Task et d'effectuer les appels externes via le même pool de threads qui gère les demandes des utilisateurs. C'est pour éviter le scénario précédent.
la source