290 votes

Je viens de découvrir pourquoi tous les sites Web ASP.Net sont lents, et j'essaie de trouver une solution.

Je viens de découvrir que chaque requête dans une application web ASP.Net obtient un verrou de session au début de la requête, puis le libère à la fin de la requête !

Au cas où vous n'auriez pas compris, comme je l'ai fait au début, cela signifie essentiellement ce qui suit :

  • Chaque fois qu'une page Web ASP.Net met beaucoup de temps à se charger (peut-être en raison d'un appel lent à la base de données ou autre), et que l'utilisateur décide de naviguer vers une autre page parce qu'il est fatigué d'attendre, il ne le peut pas ! Le verrouillage de session ASP.Net oblige la nouvelle demande de page à attendre que la demande originale ait terminé son chargement péniblement lent. Arrrgh.

  • Chaque fois qu'un UpdatePanel se charge lentement, et que l'utilisateur décide de naviguer vers une autre page avant que l'UpdatePanel ait fini de se mettre à jour... IL NE PEUT PAS ! Le verrouillage de session ASP.net oblige la nouvelle demande de page à attendre que la demande originale ait terminé son chargement douloureusement lent. Double Arrrgh !

Quelles sont les options ? Jusqu'à présent, j'ai trouvé :

  • Implémentez un SessionStateDataStore personnalisé, que ASP.Net prend en charge. Je n'en ai pas trouvé beaucoup à copier, et cela semble plutôt risqué et facile à gâcher.
  • Gardez la trace de toutes les demandes en cours, et si une demande arrive du même utilisateur, annulez la demande initiale. Cela semble un peu extrême, mais cela pourrait fonctionner (je pense).
  • N'utilisez pas la Session ! Lorsque j'ai besoin d'une sorte d'état pour l'utilisateur, je pourrais simplement utiliser Cache à la place, et les éléments clés sur le nom d'utilisateur authentifié, ou quelque chose comme ça. Encore une fois, cela semble un peu extrême.

Je ne peux vraiment pas croire que l'équipe ASP.Net de Microsoft ait laissé un tel goulot d'étranglement dans le cadre de travail à la version 4.0 ! Est-ce que j'ai raté quelque chose d'évident ? Serait-il difficile d'utiliser une collection ThreadSafe pour la session ?

41 votes

Vous réalisez que ce site est construit à partir de .NET. Cela dit, je pense qu'il s'adapte très bien.

7 votes

OK, j'étais un peu facétieux avec mon titre. Néanmoins, l'effondrement des performances qu'impose l'implémentation immédiate de session est surprenant. De plus, je parie que les gars de Stack Overflow ont dû faire un bon bout de travail de développement hautement personnalisé pour obtenir les performances et l'extensibilité qu'ils ont obtenues - et bravo à eux. Enfin, Stack Overflow est une application MVC, et non WebForms, ce qui, je le parie, aide (même s'il faut admettre qu'ils utilisent toujours la même infrastructure de session).

4 votes

204voto

Joel Mueller Points 14985

Si votre page ne modifie aucune variable de session, vous pouvez vous passer de la plupart de ces verrouillages.

<% @Page EnableSessionState="ReadOnly" %>

Si votre page ne lit pas de variables de session, vous pouvez désactiver complètement ce verrou pour cette page.

<% @Page EnableSessionState="False" %>

Si aucune de vos pages n'utilise de variables de session, il suffit de désactiver l'état de session dans le web.config.

<sessionState mode="Off" />

Je suis curieux, que pensez-vous qu'une "collection ThreadSafe" devrait faire pour devenir thread-safe, si elle n'utilise pas de verrous ?

Edit : Je devrais probablement expliquer ce que j'entends par "se retirer de la plupart de ce verrou". Un nombre quelconque de pages en lecture seule ou sans session peuvent être traitées en même temps pour une session donnée sans se bloquer mutuellement. Cependant, une page en lecture-écriture-session ne peut pas commencer à être traitée avant que toutes les requêtes en lecture seule soient terminées, et pendant qu'elle est en cours d'exécution, elle doit avoir un accès exclusif à la session de cet utilisateur afin de maintenir la cohérence. Le verrouillage de valeurs individuelles ne fonctionnerait pas, car que se passe-t-il si une page modifie un ensemble de valeurs connexes en tant que groupe ? Comment s'assurer que les autres pages exécutées en même temps obtiennent une vue cohérente des variables de la session de l'utilisateur ?

Je vous suggère d'essayer de minimiser la modification des variables de session une fois qu'elles ont été définies, si possible. Cela vous permettrait de faire en sorte que la majorité de vos pages soient des pages en lecture seule, ce qui augmenterait les chances que plusieurs requêtes simultanées du même utilisateur ne se bloquent pas mutuellement.

2 votes

Salut Joel Merci pour votre temps sur cette réponse. Ce sont de bonnes suggestions et des pistes de réflexion. Je ne comprends pas votre raisonnement pour dire que toutes les valeurs d'une session doivent être verrouillées de manière exclusive sur l'ensemble de la requête. Les valeurs du cache d'ASP.Net peuvent être modifiées à tout moment par n'importe quel thread. Pourquoi cela devrait-il être différent pour la session ? En passant, un problème que je rencontre avec l'option readonly est que si un développeur ajoute une valeur à la session alors qu'elle est en mode readonly, il échoue silencieusement (sans exception). En fait, il conserve la valeur pour le reste de la requête, mais pas au-delà.

5 votes

@James - Je ne fais que deviner les motivations des concepteurs ici, mais j'imagine qu'il est plus courant d'avoir plusieurs valeurs dépendant les unes des autres dans la session d'un seul utilisateur que dans un cache qui peut être purgé à tout moment pour des raisons de manque d'utilisation ou de faible mémoire. Si une page définit 4 variables de session liées, et qu'une autre les lit après que deux seulement ont été modifiées, cela pourrait facilement conduire à des bogues très difficiles à diagnostiquer. J'imagine que les concepteurs ont choisi de considérer "l'état actuel de la session d'un utilisateur" comme une seule unité à des fins de verrouillage pour cette raison.

0 votes

En outre, n'oubliez pas que lorsque vous utilisez StateServer ou les fournisseurs d'état de session de SQL Server, toutes les variables de session d'un utilisateur donné sont sérialisées et envoyées au serveur dans un seul blob binaire, à la fin d'une requête. Essayer de synchroniser les variables individuelles au sein de ce blob, au fur et à mesure qu'elles sont modifiées en temps réel, serait assez terrible pour les performances. Cela signifierait potentiellement des dizaines d'appels au serveur par requête, au lieu d'une seule lecture et d'une seule écriture.

85voto

James Points 2551

OK, un grand bravo à Joel Muller pour sa contribution. Ma solution finale était d'utiliser le module Custom SessionStateModule détaillé à la fin de cet article MSDN :

http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx

C'était :

  • Très rapide à mettre en œuvre (cela semblait même plus facile que de passer par le fournisseur).
  • Utilisation d'une grande partie de la gestion de session standard d'ASP.Net (via la classe SessionStateUtility).

Cela a fait une énorme différence dans l'impression de "vivacité" de notre application. Je n'arrive toujours pas à croire que l'implémentation personnalisée de ASP.Net Session verrouille la session pour toute la requête. Cela ajoute une telle quantité de lenteur aux sites Web. À en juger par la quantité de recherches en ligne que j'ai dû faire (et les conversations avec plusieurs développeurs ASP.Net très expérimentés), beaucoup de gens ont connu ce problème, mais très peu en ont découvert la cause. Je vais peut-être écrire une lettre à Scott Gu...

J'espère que cela aidera quelques personnes !

19 votes

Cette référence est une trouvaille intéressante, mais je dois vous mettre en garde sur quelques points - l'exemple de code présente quelques problèmes : Premièrement, ReaderWriterLock a été déprécié au profit de ReaderWriterLockSlim - vous devriez l'utiliser à la place. Deuxièmement, lock (typeof(...)) a également été déprécié - vous devriez plutôt verrouiller sur une instance d'objet statique privée. Troisièmement, la phrase "Cette application n'empêche pas les requêtes Web simultanées d'utiliser le même identifiant de session" est un avertissement et non une fonctionnalité.

1 votes

Imaginons que la PageA et la PageB soient toutes deux exécutées en même temps, dans la même session utilisateur. Les deux pages modifient les variables de la session. La page A se termine légèrement avant la page B. En utilisant ce module d'état de session personnalisé, que se passe-t-il lorsque les deux pages modifient la même variable de session en même temps ? Ce module transmettra la même SessionStateItemCollection aux deux pages. Cette classe est documentée comme n'étant pas thread-safe. De mauvaises choses s'ensuivent sous charge.

3 votes

Je pense que vous pouvez faire en sorte que cela fonctionne, mais vous devez remplacer l'utilisation de SessionStateItemCollection dans l'échantillon de code avec une classe thread-safe (peut-être basée sur ConcurrentDictionary ) si vous voulez éviter les erreurs difficiles à reproduire sous charge.

31voto

gregmac Points 12813

J'ai commencé à utiliser le AngiesList.Redis.RedisSessionStateModule qui, en plus de l'utilisation de la (très rapide) Serveur Redis pour le stockage (j'utilise le Portail Windows -- bien qu'il existe également un Port MSOpenTech ), il n'effectue absolument aucun verrouillage sur la session.

À mon avis, si votre application est structurée de manière raisonnable, ce n'est pas un problème. Si vous avez réellement besoin de données verrouillées et cohérentes dans le cadre de la session, vous devez spécifiquement implémenter un contrôle de verrouillage/de concordance par vos propres moyens.

La décision de MS de verrouiller par défaut toutes les sessions ASP.NET pour pallier à une mauvaise conception des applications est une mauvaise décision, à mon avis. D'autant plus qu'il semble que la plupart des développeurs n'aient pas réalisé ou ne réalisent pas que les sessions étaient verrouillées, sans parler du fait que les applications doivent apparemment être structurées de manière à ce que l'état de la session soit en lecture seule autant que possible (opt-out, si possible).

0 votes

Votre lien GitHub semble être 404-dead. libraries.io/github/angieslist/AL-Redis semble être la nouvelle URL ?

1 votes

On dirait que l'auteur a voulu supprimer la bibliothèque, même dans le deuxième lien. J'hésiterais à utiliser une bibliothèque abandonnée, mais il y a une bifurcation ici : github.com/PrintFleet/AL-Redis et une bibliothèque alternative liée à partir d'ici : stackoverflow.com/a/10979369/12534

21voto

Der_Meister Points 336

J'ai préparé une bibliothèque à partir des liens postés dans ce fil. Elle utilise les exemples de MSDN et CodeProject. Merci à James.

J'ai également apporté des modifications conseillées par Joel Mueller.

Le code est ici :

https://github.com/dermeister0/LockFreeSessionState

Module HashTable :

Install-Package Heavysoft.LockFreeSessionState.HashTable

Module ScaleOut StateServer :

Install-Package Heavysoft.LockFreeSessionState.Soss

Module personnalisé :

Install-Package Heavysoft.LockFreeSessionState.Common

Si vous voulez implémenter le support de Memcached ou Redis, installez ce paquet. Ensuite, héritez du module LockFreeSessionStateModule et mettre en œuvre des méthodes abstraites.

Le code n'est pas encore testé en production. Il faut également améliorer la gestion des erreurs. Les exceptions ne sont pas capturées dans l'implémentation actuelle.

Quelques fournisseurs de sessions sans verrouillage utilisant Redis :

0 votes

Il nécessite des bibliothèques de la solution ScaleOut, qui n'est pas gratuite ?

1 votes

Oui, j'ai créé une implémentation pour SOSS uniquement. Vous pouvez utiliser les fournisseurs de session Redis mentionnés, ils sont gratuits.

0 votes

Hoàng Long n'a peut-être pas compris que vous avez le choix entre l'implémentation de la table de hachage en mémoire et le StateServer ScaleOut.

11voto

George Mavritsakis Points 2778

À moins que votre application n'ait des besoins particuliers, je pense que vous avez deux approches :

  1. Ne pas utiliser de session du tout
  2. Utilisez la session telle quelle et effectuez les réglages fins comme l'a mentionné Joel.

La session est non seulement thread-safe mais aussi state-safe, en ce sens que vous savez que tant que la requête en cours n'est pas terminée, chaque variable de session ne sera pas modifiée par une autre requête active. Pour ce faire, vous devez garantir cette session SERONT VERROUILLÉS jusqu'à ce que la demande en cours soit terminée.

Vous pouvez créer un comportement semblable à celui d'une session de plusieurs façons, mais s'il ne verrouille pas la session actuelle, il ne s'agit pas d'une "session".

Pour les problèmes spécifiques que vous avez mentionnés, je pense que vous devriez vérifier HttpContext.Current.Response.IsClientConnected . Cela peut être utile pour éviter les exécutions et les attentes inutiles sur le client, bien que cela ne puisse pas résoudre ce problème entièrement, car cela ne peut être utilisé que par une méthode de pooling et non asynchrone.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X