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 d’une demande, puis le libère à la fin de la demande!

Au cas où les implications de ceci seraient perdues sur vous, comme cela a été le cas pour moi au début, cela signifie essentiellement ce qui suit:

Alors, quelles sont les options? Jusqu’à présent, j’ai trouvé:

  • Implémentez un SessionStateDataStore personnalisé, pris en charge par ASP.Net. Je n’ai pas trouvé trop de choses à copier, et cela semble risqué et facile à gâcher.
  • Gardez une trace de toutes les demandes en cours, et si une demande provient du même utilisateur, annulez la demande initiale. Semble un peu extrême, mais ça marcherait (je pense).
  • N’utilisez pas la session! Lorsque j’ai besoin d’une sorte d’état pour l’utilisateur, je peux simplement utiliser Cache et des éléments clés sur le nom d’utilisateur authentifié, ou quelque chose du genre. Encore une fois, semble un peu extrême.

Je ne peux vraiment pas croire que l’équipe Microsoft ASP.Net aurait laissé un tel goulot d’étranglement dans la version 4.0! Est-ce que je manque quelque chose d’évident? À quel point serait-il difficile d’utiliser une collection ThreadSafe pour la session?

    Si votre page ne modifie aucune variable de session, vous pouvez désactiver la plupart de ces verrous.

    <% @Page EnableSessionState="ReadOnly" %> 

    Si votre page ne lit aucune variable 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, désactivez simplement l’état de la session dans le fichier web.config.

      

    Je suis curieux, que pensez-vous que “une collection ThreadSafe” ferait pour devenir thread-safe, si elle n’utilise pas de verrous?

    Edit: Je devrais probablement expliquer par ce que je veux dire par “se retirer de la plupart de cette serrure”. Un nombre quelconque de pages en lecture seule ou en session sans session peut être traité pour une session donnée en même temps sans se bloquer. Cependant, une page de lecture-écriture-session ne peut pas démarrer tant que toutes les requêtes en lecture seule ne sont pas terminées et, pendant son exécution, elle doit avoir un access exclusif à la session de cet utilisateur afin de maintenir la cohérence. Verrouiller des valeurs individuelles ne fonctionnerait pas, car si une page modifiait un ensemble de valeurs associées en tant que groupe? Comment vous assureriez-vous que les autres pages exécutées en même temps obtiennent une vue cohérente des variables de 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 rendre la majorité de vos pages en lecture seule, augmentant ainsi le risque que plusieurs requêtes simultanées provenant du même utilisateur ne se bloquent pas.

    OK, tellement gros accessoires à Joel Muller pour toutes ses consortingbutions. Ma solution ultime consistait à utiliser le module SessionStateModule personnalisé détaillé à la fin de cet article MSDN:

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

    C’était:

    • Mise en œuvre très rapide
    • Utilisé beaucoup de la gestion de session ASP.Net standard prête à l’emploi (via la classe SessionStateUtility)

    Cela a fait une énorme différence au sentiment de “snapiness” à notre application. Je n’arrive toujours pas à croire que l’implémentation personnalisée de la session ASP.Net verrouille la session pour toute la demande. Cela ajoute une telle quantité de lenteur aux sites Web. A en juger par la quantité de recherches en ligne que j’ai eu à faire (et par des conversations avec plusieurs développeurs ASP.Net très expérimentés), beaucoup de personnes ont rencontré ce problème, mais très peu de personnes ont déjà trouvé la cause. Je vais peut-être écrire une lettre à Scott Gu …

    J’espère que cela aidera quelques personnes là-bas!

    J’ai commencé à utiliser AngiesList.Redis.RedisSessionStateModule , qui en plus d’utiliser le serveur Redis (très rapide) pour le stockage (j’utilise le port Windows – bien qu’il y ait aussi un port MSOpenTech ), il ne verrouille absolument pas la session .

    À mon avis, si votre application est structurée de manière raisonnable, cela ne pose aucun problème. Si vous avez réellement besoin de données cohérentes et verrouillées dans le cadre de la session, vous devez implémenter spécifiquement une vérification de locking / simultanéité.

    MS décide que chaque session ASP.NET doit être verrouillée par défaut, uniquement pour gérer une conception médiocre des applications, est une mauvaise décision, à mon avis. Surtout parce qu’il semble que la plupart des développeurs ne réalisent pas / ne réalisent même pas que les sessions sont verrouillées, et encore moins que les applications doivent apparemment être structurées pour que vous puissiez effectuer un état de session en lecture seule (désactivation si possible) .

    J’ai préparé une bibliothèque basée sur les liens postés dans ce fil. Il 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 souhaitez implémenter la prise en charge de Memcached ou Redis, installez ce package. Ensuite, héritez de la classe LockFreeSessionStateModule et implémentez des méthodes abstraites.

    Le code n’est pas encore testé en production. Vous devez également améliorer la gestion des erreurs. Les exceptions ne sont pas sockets en compte dans la mise en œuvre actuelle.

    Certains fournisseurs de session sans verrou utilisant Redis:

    À moins que votre application ait spécialement besoin, je pense que vous avez 2 approches:

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

    La session est non seulement sécurisée pour les threads, mais aussi sécurisée, de manière à ce que vous sachiez que tant que la requête en cours n’est pas terminée, chaque variable de session ne change pas d’une autre requête active. Pour que cela se produise, vous devez vous assurer que la session SERA VERROUILLÉE jusqu’à ce que la demande en cours soit terminée.

    Vous pouvez créer un comportement de type session de plusieurs manières, mais s’il ne verrouille pas la session en cours, il ne s’agira 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 des exécutions inutiles et attendre sur le client, même si cela ne peut pas résoudre entièrement ce problème, car il ne peut être utilisé que par un pooling et non par une asynchrone.

    Pour ASPNET MVC, nous avons fait ce qui suit:

    1. Par défaut, définissez SessionStateBehavior.ReadOnly sur l’action de tous les contrôleurs en remplaçant DefaultControllerFactory
    2. Sur les actions du contrôleur qui ont besoin d’écrire dans l’état de session, cochez avec l’atsortingbut pour le définir sur SessionStateBehaviour.Required

    Créez Custom ControllerFactory et remplacez GetControllerSessionBehaviour .

      protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType) { var DefaultSessionStateBehaviour = SessionStateBehaviour.ReadOnly; if (controllerType == null) return DefaultSessionStateBehaviour; var isRequireSessionWrite = controllerType.GetCustomAtsortingbutes(inherit: true).FirstOrDefault() != null; if (isRequireSessionWrite) return SessionStateBehavior.Required; var actionName = requestContext.RouteData.Values["action"].ToSsortingng(); MethodInfo actionMethodInfo; try { actionMethodInfo = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); } catch (AmbiguousMatchException) { var httpRequestTypeAttr = GetHttpRequestTypeAttr(requestContext.HttpContext.Request.HttpMethod); actionMethodInfo = controllerType.GetMethods().FirstOrDefault( mi => mi.Name.Equals(actionName, SsortingngComparison.CurrentCultureIgnoreCase) && mi.GetCustomAtsortingbutes(httpRequestTypeAttr, false).Length > 0); } if (actionMethodInfo == null) return DefaultSessionStateBehaviour; isRequireSessionWrite = actionMethodInfo.GetCustomAtsortingbutes(inherit: false).FirstOrDefault() != null; return isRequireSessionWrite ? SessionStateBehavior.Required : DefaultSessionStateBehaviour; } private static Type GetHttpRequestTypeAttr(ssortingng httpMethod) { switch (httpMethod) { case "GET": return typeof(HttpGetAtsortingbute); case "POST": return typeof(HttpPostAtsortingbute); case "PUT": return typeof(HttpPutAtsortingbute); case "DELETE": return typeof(HttpDeleteAtsortingbute); case "HEAD": return typeof(HttpHeadAtsortingbute); case "PATCH": return typeof(HttpPatchAtsortingbute); case "OPTIONS": return typeof(HttpOptionsAtsortingbute); } throw new NotSupportedException("unable to determine http method"); } 

    AcquisitionSessionLockAtsortingbute

     [AtsortingbuteUsage(AtsortingbuteTargets.Method)] public sealed class AcquireSessionLock : Atsortingbute { } 

    Connectez la fabrique de contrôleurs créée dans global.asax.cs

     ControllerBuilder.Current.SetControllerFactory(typeof(DefaultReadOnlySessionStateControllerFactory)); 

    Maintenant, nous pouvons avoir l’état de session en read-only et en read-write dans un seul Controller .

     public class TestController : Controller { [AcquireSessionLock] public ActionResult WriteSession() { var timeNow = DateTimeOffset.UtcNow.ToSsortingng(); Session["key"] = timeNow; return Json(timeNow, JsonRequestBehavior.AllowGet); } public ActionResult ReadSession() { var timeNow = Session["key"]; return Json(timeNow ?? "empty", JsonRequestBehavior.AllowGet); } } 

    Remarque: L’état de la session ASPNET peut toujours être écrit même en mode lecture seule et ne générera aucune forme d’exception (il ne verrouille que pour garantir la cohérence). Nous devons donc faire attention à marquer AcquireSessionLock dans les actions du contrôleur nécessitant un état d’écriture. .

    Marquer l’état de session d’un contrôleur comme en lecture seule ou désactivé résoudra le problème.

    Vous pouvez décorer un contrôleur avec l’atsortingbut suivant pour le marquer en lecture seule:

     [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)] 

    le System.Web.SessionState.SessionStateBehavior enum a les valeurs suivantes:

    • Défaut
    • désactivé
    • Lecture seulement
    • Champs obligatoires

    Juste pour aider quelqu’un avec ce problème (locking des demandes lors de l’exécution d’un autre de la même session) …

    Aujourd’hui, j’ai commencé à résoudre ce problème et, après quelques heures de recherche, je l’ai résolu en supprimant la méthode Session_Start (même si elle était vide) du fichier Global.asax .

    Cela fonctionne dans tous les projets que j’ai testés.