Je viens de découvrir pourquoi tout ASP.Net les sites Web sont lents, et j'essaie de trouver quoi faire.
je viens de découvrir que chaque requête dans un ASP.Net l'application web obtient un verrouillage de Session au début d'une requête, puis la libère à la fin de la requête!
dans le cas où les implications de ceci sont perdues sur vous, comme il était pour moi au début, cela signifie fondamentalement ce qui suit:
-
Anytime anytime ASP.Net page Web prend beaucoup de temps à charger (peut-être en raison d'un appel de base de données lent ou autre), et l'utilisateur décide qu'ils veulent naviguer à une page différente parce qu'ils sont fatigués d'attendre, ils ne peuvent pas! Les ASP.Net le verrouillage de session force la nouvelle requête de page à attendre jusqu'à ce que la requête originale ait terminé sa charge douloureusement lente. Arrrgh.
-
chaque fois qu'un plan de mise à jour se charge lentement, et que l'utilisateur décide de naviguer sur une autre page avant que le plan de mise à jour ne soit terminé... ILS NE PEUVENT PAS! Les ASP.net le verrouillage de session force la nouvelle demande de page à attendre jusqu'à ce que la requête originale ait terminé sa charge douloureusement lente. Double Arrrgh!
alors quelles sont les options? Jusqu'à présent, j'ai trouvé:
- mettre en œuvre un Statatore de session personnalisé, qui ASP.Net supports. Je n'en ai pas trouvé beaucoup là-bas à copier, et ça semble un peu risqué et facile à gâcher.
- Garder une trace de toutes les demandes en cours, et si la demande vient de la même utilisateur, annulez la demande originale. Semble un peu extrême, mais cela fonctionnerait (je pense).
- N'utilisez pas Session! Quand j'ai besoin d'une sorte d'État pour l'utilisateur, je pourrais juste utiliser le Cache à la place, et les éléments clés sur le nom d'utilisateur authentifié, ou quelque chose de ce genre. Semble de nouveau nature de l'extrême.
Je ne peux vraiment pas croire que le ASP.Net Microsoft team aurait laissé un tel goulot d'étranglement de performance dans le framework à la version 4.0! Ai-je raté quelque chose d'évident? Comment serait-il difficile d'utiliser une collection ThreadSafe pour la Session?
8 réponses
si votre page ne modifie aucune des variables de session, vous pouvez désactiver la plupart de ce verrouillage.
<% @Page EnableSessionState="ReadOnly" %>
si votre page ne lit pas de variables de session, vous pouvez vous désinscrire entièrement de 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 "une collection ThreadSafe" ferait à devenir thread-safe, si elle n'utilise pas de serrures?
Edit: je devrais sans doute expliquer par ce que j'entends par "exclusion de la plupart de ce verrou". N'importe quel nombre de pages de session en lecture seule ou sans session peut être traité pour une session donnée en même temps sans se bloquer l'un l'autre. Cependant, une page de lecture-écriture-session ne peut pas commencer le traitement tant que toutes les requêtes en lecture seule ne sont pas 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 cohérence. Verrouiller sur des valeurs individuelles ne fonctionnerait pas, car que se passe-t-il si une page change un ensemble de valeurs liées en tant que groupe? Comment vous assureriez-vous que les autres pages qui s'exécutent en même temps obtiendraient une vue cohérente des variables de session de l'utilisateur?
je suggère 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 faire la majorité de vos pages de session en lecture seule, augmentant la chance que plusieurs requêtes simultanées provenant du même utilisateur ne se bloqueraient pas.
Ok, donc Gros accessoires à Joel Muller pour tous ses commentaires. Ma solution ultime a été d'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 (en fait semblait plus facile que de passer par le fournisseur)
- Utilisé beaucoup de la norme ASP.Net manipulation de la session hors de la boîte (via la classe de sécurité Sessionstatutility)
cela a fait une énorme différence au sentiment de" snapiness " à notre application. Je ne peux toujours pas croire la mise en œuvre sur mesure de ASP.Net la Session ferme la session pour l'ensemble de la requête. Cela ajoute une telle quantité énorme d'atonie aux sites web. À en juger par la quantité de recherche en ligne que j'ai eu à faire (et les conversations avec plusieurs expérimenté ASP.Net développeurs), beaucoup de gens ont connu ce problème, mais très peu de gens ont jamais atteint le fond de la cause. Peut-être que je vais écrire une lettre à Scott gu...
j'espère que cette aide quelques personnes!
j'ai commencé à utiliser le AngiesList.Redis.RedisSessionStateModule , qui en plus d'utiliser le (très rapide) redis server pour le stockage (j'utilise le Windows port - bien qu'il y ait aussi un MSOpenTech port ), il ne ferme absolument pas la session.
à mon avis, si votre demande est structurée de façon raisonnable, ce n'est pas un problème. Si vous en fait besoin verrouillé, des données cohérentes dans le cadre de la session, vous devriez spécifiquement mettre en œuvre une vérification de verrouillage/concurrence sur votre propre.
ms deciding that every ASP.NET la session devrait être verrouillée par défaut juste pour traiter la mauvaise conception de l'application est une mauvaise décision, à mon avis. Surtout parce qu'il semble que la plupart des développeurs n'ont pas/ne réalisent même pas que les sessions étaient verrouillées, et encore moins que les applications ont apparemment besoin d'être structurées de sorte que vous puissiez faire de l'état de session en lecture seule autant dans la mesure du possible (opt-out, si possible).
j'ai préparé une bibliothèque basée sur des liens postés dans ce fil. Il utilise les exemples de MSDN et de Codeprojet. Grâce à James.
j'ai aussi fait des modifications conseillées par Joel Mueller.
Le Codeest ici:
https://github.com/dermeister0/LockFreeSessionState
Hashtable module:
Install-Package Heavysoft.LockFreeSessionState.HashTable
Module ScaleOut Staterver:
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. Puis hériter de la classe LockFreeSessionStateModule et mettre en œuvre des méthodes abstraites.
le code n'est pas encore testé en production. Il faut aussi améliorer le traitement des erreurs. Les Exceptions sont pas pris dans la mise en œuvre actuelle.
certains fournisseurs de session sans verrouillage utilisant Redis:
- https://github.com/angieslist/AL-Redis (suggéré par gregmac dans ce fil.)
- https://github.com/welegan/RedisSessionProvider (NuGet: RedisSessionProvider)
- https://github.com/efaruk/playground/tree/master/UnlockedStateProvider (NuGet: UnlockedStateProvider.Redis)
sauf si votre application a des besoins spéciaux, je pense que vous avez 2 approches:
- Ne pas utiliser de session à tous les
- utilisez la session telle quelle et effectuez le réglage fin comme joel l'a mentionné.
Session n'est pas seulement "thread-safe", mais aussi " l'état fort, d'une manière que vous savez que jusqu'à ce que la requête en cours est terminée, chaque variable de session ne changeront pas à partir d'une autre requête active. Pour que cela arrive, vous doit s'assurer que la session sera verrouillée jusqu'à ce que la demande actuelle soit terminée.
vous pouvez créer une session comme behavior de plusieurs façons, mais si elle ne verrouille pas la session actuelle, elle ne sera pas 'session'.
pour les problèmes spécifiques que vous avez mentionnés, je pense que vous devriez cocher HttpContext.Actuel.Réponse.IsClientConnected . Cela peut être utile pour prévenir les exécutions et attend le client, bien qu'il ne puisse pas résoudre entièrement ce problème, car il ne peut être utilisé que par une méthode de mise en commun et non asynchrone.
pour ASPNET MVC, nous avons fait ce qui suit:
- par défaut, définissez
SessionStateBehavior.ReadOnly
sur toutes les actions du contrôleur en remplaçantDefaultControllerFactory
- sur les actions du contrôleur qui ont besoin d'écrire à l'état de session, marquez avec l'attribut pour le définir à
SessionStateBehaviour.Required
créer custom ControllerFactory et outrepasser 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");
}
AcquireSessionLockAttribute
[AttributeUsage(AttributeTargets.Method)]
public sealed class AcquireSessionLock : Attribute
{ }
brancher l'usine de contrôleur créé dans global.asax.cs
ControllerBuilder.Current.SetControllerFactory(typeof(DefaultReadOnlySessionStateControllerFactory));
maintenant, nous pouvons avoir les deux états de session read-only
et read-write
dans un seul 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);
}
}
Note: L'état de session ASPNET peut encore être écrit même en lecture seule mode et ne lancera aucune forme d'exception (il ne se verrouille tout simplement pas à garantir la cohérence), de sorte que nous devons faire attention à marquer
AcquireSessionLock
dans les actions du contrôleur qui nécessitent l'état de session d'écriture.
marquer l'état de session d'un contrôleur comme readonly ou disabled 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 système .Web.SessionState.SessionStateBehavior enum a les valeurs suivantes:
- par Défaut
- Disabled
- ReadOnly
- requis
juste pour aider quelqu'un avec ce problème (verrouillage des requêtes lors de l'exécution d'une autre depuis 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 vide) de la globale.asax "151950920 de fichier".
cela fonctionne dans tous les projets que j'ai testés.