web-dev-qa-db-fra.com

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 demande 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 pour vous, comme ce fut le cas pour moi au début, cela signifie essentiellement ce qui suit:

  • Chaque fois qu'une page Web ASP.Net prend du temps à se charger (peut-être à cause d'un appel de base de données lent ou autre), et que l'utilisateur décide de naviguer vers une page différente car il est fatigué d'attendre, ILS NE PEUVENT PAS! Le verrou de session ASP.Net oblige la nouvelle demande de page à attendre que la demande initiale ait terminé son chargement extrêmement lent. Arrrgh.

  • Chaque fois qu'un UpdatePanel se charge lentement et que l'utilisateur décide de passer à une page différente avant la fin de la mise à jour de UpdatePanel ... ILS NE PEUVENT PAS! Le verrou de session ASP.net force la nouvelle demande de page à attendre que la demande initiale ait terminé son chargement extrêmement lent. Double Arrrgh!

Alors, quelles sont les options? Jusqu'à présent je suis venu avec:

  • Implémentez un SessionStateDataStore personnalisé, pris en charge par ASP.Net. Je n'en ai pas trouvé beaucoup à copier, et cela me semble plutôt 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. Cela semble assez extrême, mais cela fonctionnerait (je pense).
  • N'utilisez pas la session! Lorsque j'ai besoin d'un type d'état pour l'utilisateur, je peux simplement utiliser le cache à la place, ainsi que des éléments de clé 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 goulet d'étranglement dans le cadre de la version 4.0! Est-ce que je manque quelque chose d'évident? Dans quelle mesure serait-il difficile d'utiliser une collection ThreadSafe pour la session?

270
James

OK, donc gros accessoires à Joel Muller pour toute sa contribution. Ma solution ultime consistait à utiliser le 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 en fait plus facile que de choisir la voie du fournisseur)
  • Utilisé une grande partie de la gestion de session ASP.Net standard prête à l'emploi (via la classe SessionStateUtility)

Cela a fait une énorme différence dans le sentiment de "rapidité" de notre application. Je n'arrive toujours pas à croire que l'implémentation personnalisée d'ASP.Net Session verrouille la session pour toute la demande. Cela ajoute une énorme quantité de lenteur aux sites Web. À en juger par la quantité de recherches en ligne que j’avais à faire (et des conversations avec plusieurs développeurs vraiment expérimentés d’ASP.Net), beaucoup de personnes ont été confrontées à ce problème, mais très peu de personnes sont allées au fond des choses. J'écrirai peut-être une lettre à Scott Gu ...

J'espère que cela aide quelques personnes là-bas!

82
James

Si votre page ne modifie aucune variable de session, vous pouvez désactiver la majeure partie de ce verrou.

<% @Page EnableSessionState="ReadOnly" %>

Si votre page ne lit aucune variable de session, vous pouvez entièrement désactiver 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 session dans le fichier web.config.

<sessionState mode="Off" />

Je suis curieux, que pensez-vous qu'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 "retirer de la plupart de ce verrou". N'importe quel nombre de pages en lecture seule ou sans session peut être traité pour une session donnée en même temps sans se bloquer. Toutefois, une page de session en lecture-écriture ne peut pas démarrer le traitement tant que toutes les demandes en lecture seule ne sont pas terminée. Pour pouvoir être cohérente, elle doit disposer d'un accès exclusif à la session de cet utilisateur. Verrouiller des valeurs individuelles ne fonctionnerait pas, car si une page modifiait un ensemble de valeurs associées en tant que groupe? Comment garantiriez-vous que les autres pages s'exécutant simultanément obtiennent une vue cohérente des variables de session de l'utilisateur?

Je suggérerais que vous essayiez de minimiser la modification des variables de session une fois qu'elles ont été définies, si possible. Cela vous permettrait de créer la majorité de vos pages en lecture seule, augmentant ainsi le risque que plusieurs demandes simultanées émanant du même utilisateur ne se bloquent pas.

199
Joel Mueller

J'ai commencé à utiliser le AngiesList.Redis.RedisSessionStateModule , à part l'utilisation du (très rapide) serveur Redis 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 demande est structurée de manière raisonnable, cela ne pose pas de problème. Si vous avez réellement besoin de données cohérentes verrouillées dans le cadre de la session, vous devez spécifiquement implémenter vous-même une vérification de verrouillage/concurrence.

MS décide à mon avis que chaque session ASP.NET doit être verrouillée par défaut uniquement pour gérer une conception d'application médiocre. C'est une mauvaise décision. 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, sans parler du fait que les applications doivent apparemment être structurées pour que vous puissiez effectuer l'état de session en lecture seule autant que possible (désinscription, dans la mesure du possible) .

31
gregmac

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. Puis héritez de la classe LockFreeSessionStateModule et implémentez des méthodes abstraites.

Le code n'a pas encore été testé en production. Également besoin d'améliorer la gestion des erreurs. Les exceptions ne sont pas prises en compte dans la mise en œuvre actuelle.

Certains fournisseurs de session sans verrouillage utilisant Redis:

20
Der_Meister

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

  1. Ne pas utiliser la session du tout
  2. Utilisez la session telle quelle et effectuez les réglages nécessaires, comme mentionné par joel.

La session est non seulement sécurisée pour les threads, mais également pour les états, de sorte que vous sachiez que tant que la requête en cours n'est pas terminée, chaque variable de session ne sera pas modifiée à partir d'une autre requête active. Pour que cela se produise, vous devez assurer que la session SERA VERROUILLÉE jusqu'à ce que la demande en cours soit terminée.

Vous pouvez créer une session semblable à un comportement de plusieurs façons, mais si elle ne verrouille pas la session en cours, elle ne sera pas "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 qu'il ne puisse pas résoudre entièrement ce problème, car cela ne peut être utilisé que par un moyen de regroupement et non asynchrone.

11

Si vous utilisez le Microsoft.Web.RedisSessionStateProvider mis à jour (à partir de 3.0.2), vous pouvez l'ajouter à votre web.config pour autoriser les sessions simultanées.

<appSettings>
    <add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>

Source

6
rabz100

Pour ASPNET MVC, nous avons procédé comme suit:

  1. Par défaut, définissez SessionStateBehavior.ReadOnly sur toutes les actions du contrôleur en remplaçant DefaultControllerFactory
  2. Pour les actions du contrôleur nécessitant une écriture en état de session, cochez la case avec l'attribut pour le définir sur SessionStateBehaviour.Required

Créez un ControllerFactory personnalisé et remplacez GetControllerSessionBehaviour.

    protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
    {
        var DefaultSessionStateBehaviour = SessionStateBehaviour.ReadOnly;

        if (controllerType == null)
            return DefaultSessionStateBehaviour;

        var isRequireSessionWrite =
            controllerType.GetCustomAttributes<AcquireSessionLock>(inherit: true).FirstOrDefault() != null;

        if (isRequireSessionWrite)
            return SessionStateBehavior.Required;

        var actionName = requestContext.RouteData.Values["action"].ToString();
        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, StringComparison.CurrentCultureIgnoreCase) && mi.GetCustomAttributes(httpRequestTypeAttr, false).Length > 0);
        }

        if (actionMethodInfo == null)
            return DefaultSessionStateBehaviour;

        isRequireSessionWrite = actionMethodInfo.GetCustomAttributes<AcquireSessionLock>(inherit: false).FirstOrDefault() != null;

         return isRequireSessionWrite ? SessionStateBehavior.Required : DefaultSessionStateBehaviour;
    }

    private static Type GetHttpRequestTypeAttr(string httpMethod) 
    {
        switch (httpMethod)
        {
            case "GET":
                return typeof(HttpGetAttribute);
            case "POST":
                return typeof(HttpPostAttribute);
            case "PUT":
                return typeof(HttpPutAttribute);
            case "DELETE":
                return typeof(HttpDeleteAttribute);
            case "HEAD":
                return typeof(HttpHeadAttribute);
            case "PATCH":
                return typeof(HttpPatchAttribute);
            case "OPTIONS":
                return typeof(HttpOptionsAttribute);
        }

        throw new NotSupportedException("unable to determine http method");
    }

AcquérirSessionLockAttribute

[AttributeUsage(AttributeTargets.Method)]
public sealed class AcquireSessionLock : Attribute
{ }

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

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

Maintenant, nous pouvons avoir les états read-only et read-write dans une seule Controller.

public class TestController : Controller 
{
    [AcquireSessionLock]
    public ActionResult WriteSession()
    {
        var timeNow = DateTimeOffset.UtcNow.ToString();
        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 lève aucune forme d’exception (il ne se verrouille pas pour garantir la cohérence), nous devons donc faire attention à marquer AcquireSessionLock dans les actions du contrôleur qui nécessite l'écriture de l'état de la session.

3
Misterhex

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

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

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

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

  • Default
  • Désactivé
  • Lecture seulement
  • Champs obligatoires
3
Michael King

Juste pour aider quelqu'un avec ce problème (verrouillage des requêtes lors de l'exécution d'une autre session à partir 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 est vide) du fichier Global.asax.

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

0
graduenz