web-dev-qa-db-fra.com

Comment résoudre les problèmes d'inanition dans ASP.NET Core sur Linux (Kubernetes)?

J'exécute une API Core ASP.NET sur Linux, sur Kubernetes dans Google Cloud.

C'est une API avec une charge élevée et à chaque requête, elle exécute une bibliothèque en effectuant une opération longue (1 à 5 secondes) et gourmande en ressources processeur.

Ce que je vois, c'est qu'après le déploiement, l'API fonctionne correctement pendant un certain temps, mais au bout de 10 à 20 minutes, elle ne répond plus et même le point de contrôle de l'intégrité (qui renvoie simplement un 200 OK codé en dur) cesse de fonctionner et expire. (Cela oblige Kubernetes à tuer les pods.)

Parfois, je vois aussi le fameux message d'erreur Heartbeat took longer than "00:00:01" dans les journaux.

Googler ces phénomènes m'indique "Thrust dormed", de sorte qu'il y a trop de threads de pool de threads démarrés, ou trop de threads bloquent en attente sur quelque chose, de sorte qu'il ne reste plus de threads dans le pool pouvant capter ASP.NET Core. demandes (d'où le délai d'attente, même du point de terminaison de la vérification de l'état).

Quel est le meilleur moyen de résoudre ce problème? J'ai commencé à surveiller les nombres retournés par ThreadPool.GetMaxThreads et ThreadPool.GetAvailableThreads, mais ils sont restés constants (le port d'achèvement est toujours 1000 à la fois pour max et available et le travailleur est toujours 32767).
Y a-t-il une autre propriété que je devrais surveiller?

8
Mark Vincze

Etes-vous sûr que votre application Web ASP.NET Core est à court de threads? Il se peut que ce soit simplement en saturant toutes les ressources disponibles dans le pod, ce qui force Kubernetes à tout simplement à tuer le pod lui-même, et donc votre application Web.

J'ai vécu un scénario très similaire avec une API Web ASP.NET Core s'exécutant sous Linux RedHat dans un environnement OpenShift , qui prend également en charge le concept de pod comme dans Kubernetes: un appel nécessitait environ 1 seconde et la charge de travail, elle est d'abord devenue lente, puis insensible, ce qui a amené OpenShift à abattre le pod, et donc mon application Web.

Il se peut que votre application Web ASP.NET Core ne soit pas à court de threads, en particulier compte tenu du nombre élevé de threads de travail disponibles dans le ThreadPool. Au lieu de cela, le nombre de threads actifs associés à leurs besoins en CPU est probablement trop grandes par rapport aux millicores réellement disponibles dans le pod où ils sont exécutés: en effet, après avoir été créés, ces threads actifs sont trop nombreux pour le processeur disponible, la plupart d'entre eux finissant par être mis en file d'attente par le planificateur et en attente d'exécution, le groupe s'exécutera réellement. Le planificateur fera ensuite son travail, en s'assurant que le processeur est partagé équitablement entre les threads, en commutant fréquemment ceux qui l'utilisent. En ce qui concerne votre cas, où les threads nécessitent des de longues opérations liées au processeur, au fil du temps, les ressources sont saturées et l’application Web ne répond plus.

Une étape d’atténuation peut fournir davantage de capacité à vos pods, en particulier des millicores, ou augmenter le nombre de pods que Kubernetes peut déployer en fonction des besoins. Cependant, dans mon scénario particulier, cette approche n’a pas beaucoup aidé. Au lieu de cela, l'amélioration de l'API elle-même en réduisant l'exécution d'une requête de 1 à 300 ms a sensiblement amélioré les performances globales de l'application Web et résolu le problème.

Par exemple, si votre bibliothèque effectue les mêmes calculs dans plusieurs requêtes, vous pouvez envisager d’introduire la mise en cache sur vos structures de données afin d’accroître la vitesse au moindre coût en mémoire (ce qui a fonctionné pour moi), en particulier si vos opérations sont principalement basées sur le processeur. lié et si vous avez de telles demandes de demande à votre application Web. Vous pouvez également envisager d'activer mettre en cache la réponse dans ASP.NET Core aussi si cela convient aux charges de travail et aux réponses de votre API. ____.] À l'aide du cache, vous vous assurez que votre application Web n'exécute pas la même tâche deux fois, ce qui libère de la CPU et réduit le risque de mise en file d'attente des threads.

Le traitement plus rapide de chaque requête rendra votre application Web moins sujette au risque de saturer le processeur disponible et réduira donc le risque d'avoir trop de threads en file d'attente et en attente d'exécution.

2
smn.tino

De manière générale, le travail de longue durée est un anathème pour les applications Web. Vous voulez des temps de réponse inférieurs à une seconde pour une application Web en bonne santé. Cela est particulièrement vrai si le travail que vous devez effectuer est synchrone ou lié au processeur. Async peut au moins libérer des threads au cours du processus, mais avec le travail lié au CPU, le thread est lié.

Vous devriez décharger tout ce que vous faites sur un processus différent, puis surveiller les progrès. Pour une API, l’approche typique consiste à planifier le travail sur un processus différent, puis à renvoyer immédiatement un 202 Accepted , avec un noeud final dans le corps de la réponse que le client peut utiliser pour surveiller les progrès/obtenir le résultat final obtenu. . Vous pouvez également implémenter un Webhook, que le client peut enregistrer pour recevoir une notification indiquant que le processus est terminé, sans avoir à le vérifier en permanence.

Votre seule autre option est de consacrer plus de ressources au problème. Par exemple, vous pouvez organiser plusieurs instances derrière un équilibreur de charge, en divisant les demandes entre chaque instance afin de réduire la charge globale de chacune d'elles.

Il est également tout à fait possible que votre code présente une inefficacité ou un problème qui puisse être corrigé afin de réduire la durée du processus et/ou la consommation des ressources. Comme exemple trivial, disons que si vous utilisez quelque chose comme Task.Run, vous pourriez potentiellement libérer une tonne de threads en not . Task.Run ne devrait pratiquement jamais être utilisé dans le contexte d'une application Web. Cependant, vous n'avez posté aucun code, il est donc impossible de vous donner des indications précises.

1
Chris Pratt