it-swarm.com.de

Ich habe gerade herausgefunden, warum alle ASP.Net-Websites langsam sind, und ich versuche herauszufinden, was ich dagegen tun soll

Ich habe gerade festgestellt, dass jede Anforderung in einer ASP.Net-Webanwendung zu Beginn einer Anforderung eine Sitzungssperre erhält und am Ende der Anforderung wieder freigibt!

Falls die Implikationen davon für Sie verloren gehen, wie es zunächst für mich der Fall war, bedeutet dies im Wesentlichen Folgendes:

  • Immer wenn das Laden einer ASP.Net-Webseite sehr lange dauert (möglicherweise aufgrund eines langsamen Datenbankaufrufs oder Ähnlichem) und der Benutzer entscheidet, dass er zu einer anderen Seite navigieren möchte, weil er es leid ist zu warten, KÖNNEN SIE NICHT! Die ASP.Net-Sitzungssperre erzwingt, dass die neue Seitenanforderung wartet, bis die ursprüngliche Anforderung ihren schmerzhaft langsamen Ladevorgang beendet hat. Arrrgh.

  • Immer wenn ein UpdatePanel langsam geladen wird und der Benutzer zu einer anderen Seite navigiert, bevor das UpdatePanel die Aktualisierung abgeschlossen hat ... SIE KÖNNEN NICHT! Die ASP.net-Sitzungssperre erzwingt, dass die neue Seitenanforderung wartet, bis die ursprüngliche Anforderung ihren schmerzhaft langsamen Ladevorgang beendet hat. Doppelter Arrrgh!

Also, was sind die Optionen? Bisher habe ich mir ausgedacht:

  • Implementieren Sie einen benutzerdefinierten SessionStateDataStore, den ASP.Net unterstützt. Ich habe nicht zu viele gefunden, um sie zu kopieren, und es scheint ein hohes Risiko zu bestehen und es ist einfach, sie durcheinander zu bringen.
  • Behalten Sie alle laufenden Anfragen im Auge. Wenn eine Anfrage vom selben Benutzer eingeht, brechen Sie die ursprüngliche Anfrage ab. Scheint extrem, aber es würde funktionieren (denke ich).
  • Session nicht benutzen! Wenn ich einen Status für den Benutzer benötige, kann ich stattdessen einfach Cache und wichtige Elemente für den authentifizierten Benutzernamen oder ähnliches verwenden. Scheint wieder ein bisschen extrem.

Ich kann wirklich nicht glauben, dass das ASP.Net-Microsoft-Team bei Version 4.0 einen so großen Leistungsengpass im Framework hinterlassen hätte! Vermisse ich etwas Offensichtliches? Wie schwierig wäre es, eine ThreadSafe-Auflistung für die Sitzung zu verwenden?

270
James

OK, so ein großes Dankeschön an Joel Muller für all seine Beiträge. Meine endgültige Lösung bestand darin, das benutzerdefinierte SessionStateModule zu verwenden, das am Ende dieses MSDN-Artikels beschrieben wird:

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

Das war:

  • Sehr schnell zu implementieren (schien eigentlich einfacher zu sein als den Anbieter-Weg zu gehen)
  • Verwendete viele der standardmäßigen ASP.Net-Sitzungshandhabungen sofort (über die SessionStateUtility-Klasse)

Dies hat einen RIESIGEN Unterschied zum Gefühl der "Schnelligkeit" unserer Anwendung gemacht. Ich kann immer noch nicht glauben, dass die benutzerdefinierte Implementierung von ASP.Net Session die Sitzung für die gesamte Anforderung sperrt. Dies fügt Websites eine so große Menge an Trägheit hinzu. Gemessen an der Menge an Online-Recherchen (und Gesprächen mit mehreren wirklich erfahrenen ASP.Net-Entwicklern), die ich durchführen musste, haben viele Menschen dieses Problem erlebt, aber nur sehr wenige sind jemals der Ursache auf den Grund gegangen. Vielleicht werde ich einen Brief an Scott Gu schreiben ...

Ich hoffe das hilft ein paar Leuten da draußen!

82
James

Wenn Ihre Seite keine Sitzungsvariablen ändert, können Sie den größten Teil dieser Sperre deaktivieren.

<% @Page EnableSessionState="ReadOnly" %>

Wenn Ihre Seite keine Sitzungsvariablen liest, können Sie diese Sperre für diese Seite vollständig deaktivieren.

<% @Page EnableSessionState="False" %>

Wenn keine Ihrer Seiten Sitzungsvariablen verwendet, deaktivieren Sie einfach den Sitzungsstatus in der web.config.

<sessionState mode="Off" />

Ich bin gespannt, was Ihrer Meinung nach "eine ThreadSafe-Sammlung" tun würde, um threadsicher zu werden, wenn sie keine Sperren verwendet?

Bearbeiten: Ich sollte wahrscheinlich erklären, was ich mit "Deaktivieren der meisten dieser Sperre" meine. Es können beliebig viele Nur-Lese- oder Nicht-Sitzungsseiten für eine bestimmte Sitzung gleichzeitig verarbeitet werden, ohne sich gegenseitig zu blockieren. Eine Lese-/Schreibsitzungsseite kann jedoch erst dann mit der Verarbeitung beginnen, wenn alle schreibgeschützten Anforderungen abgeschlossen wurden. Während der Ausführung muss sie exklusiven Zugriff auf die Sitzung des Benutzers haben, um die Konsistenz zu gewährleisten. Das Sperren einzelner Werte würde nicht funktionieren, denn was passiert, wenn eine Seite eine Reihe zusammengehöriger Werte als Gruppe ändert? Wie können Sie sicherstellen, dass andere Seiten, die gleichzeitig ausgeführt werden, eine konsistente Ansicht der Sitzungsvariablen des Benutzers erhalten?

Ich würde vorschlagen, dass Sie versuchen, die Änderung von Sitzungsvariablen zu minimieren, sobald sie festgelegt wurden, sofern dies möglich ist. Auf diese Weise können Sie den Großteil Ihrer Seiten schreibgeschützt machen und die Wahrscheinlichkeit erhöhen, dass sich mehrere gleichzeitige Anforderungen desselben Benutzers nicht gegenseitig blockieren.

199
Joel Mueller

Ich habe angefangen, das AngiesList.Redis.RedisSessionStateModule zu verwenden, abgesehen von dem (sehr schnellen) Redis-Server zum Speichern (ich verwende das windows port - obwohl es auch einen MSOpenTech-Port gibt ), wird die Sitzung absolut nicht gesperrt.

Meiner Meinung nach ist dies kein Problem, wenn Ihre Bewerbung angemessen strukturiert ist. Wenn Sie tatsächlich gesperrte, konsistente Daten als Teil der Sitzung benötigen, sollten Sie eigenständig eine Sperr-/Parallelitätsprüfung durchführen.

Die Entscheidung von MS, dass jede ASP.NET-Sitzung standardmäßig gesperrt werden sollte, um schlechtes Anwendungsdesign zu behandeln, ist meiner Meinung nach eine schlechte Entscheidung. Vor allem, weil es so aussieht, als hätten die meisten Entwickler nicht einmal gemerkt, dass Sitzungen gesperrt waren, geschweige denn, dass Apps anscheinend strukturiert sein müssen, damit Sie den Sitzungsstatus so weit wie möglich schreibgeschützt gestalten können (Deaktivieren, wo möglich). .

31
gregmac

Ich habe eine Bibliothek basierend auf den in diesem Thread geposteten Links erstellt. Es verwendet die Beispiele von MSDN und CodeProject. Vielen Dank an James.

Ich habe auch Änderungen vorgenommen, die von Joel Mueller empfohlen wurden.

Code ist hier:

https://github.com/dermeister0/LockFreeSessionState

HashTable-Modul:

Install-Package Heavysoft.LockFreeSessionState.HashTable

ScaleOut StateServer-Modul:

Install-Package Heavysoft.LockFreeSessionState.Soss

Benutzerdefiniertes Modul:

Install-Package Heavysoft.LockFreeSessionState.Common

Wenn Sie die Unterstützung von Memcached oder Redis implementieren möchten, installieren Sie dieses Paket. Erben Sie dann die LockFreeSessionStateModule-Klasse und implementieren Sie abstrakte Methoden.

Der Code wurde noch nicht in der Produktion getestet. Müssen auch die Fehlerbehandlung verbessern. Ausnahmen sind in der aktuellen Implementierung nicht erfasst.

Einige sperrfreie Sitzungsanbieter, die Redis verwenden:

20
Der_Meister

Sofern Ihre Anwendung keine besonderen Anforderungen hat, haben Sie meines Erachtens zwei Ansätze:

  1. Verwenden Sie die Sitzung überhaupt nicht
  2. Verwenden Sie die Sitzung wie sie ist, und führen Sie die Feineinstellung wie angegeben durch.

Die Sitzung ist nicht nur thread-sicher, sondern auch status-sicher, sodass Sie wissen, dass sich bis zum Abschluss der aktuellen Anforderung keine Sitzungsvariable von einer anderen aktiven Anforderung ändert. Damit dies geschieht, müssen Sie sicherstellen diese Sitzung WIRD GESPERRT, bis die aktuelle Anforderung abgeschlossen ist.

Sie können ein sitzungsähnliches Verhalten auf viele Arten erstellen, aber wenn die aktuelle Sitzung nicht gesperrt wird, handelt es sich nicht um eine "Sitzung".

Für die spezifischen Probleme, die Sie angesprochen haben, sollten Sie meines Erachtens HttpContext.Current.Response.IsClientConnected überprüfen. Dies kann nützlich sein, um unnötige Ausführungen und Wartezeiten auf dem Client zu vermeiden, obwohl dies das Problem nicht vollständig lösen kann, da dies nur durch Pooling und nicht asynchron verwendet werden kann.

11

Wenn Sie den aktualisierten Microsoft.Web.RedisSessionStateProvider (Beginnend mit 3.0.2) Verwenden, können Sie diesen zu Ihrem web.config Hinzufügen, um gleichzeitige Sitzungen zuzulassen.

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

Quelle

6
rabz100

Für ASPNET MVC haben wir Folgendes ausgeführt:

  1. Standardmäßig setzen Sie SessionStateBehavior.ReadOnly Für alle Aktionen des Controllers, indem Sie DefaultControllerFactory überschreiben.
  2. Markieren Sie bei Controller-Aktionen, die in den Sitzungsstatus geschrieben werden müssen, das Attribut, um es auf SessionStateBehaviour.Required Zu setzen.

Erstellen Sie eine benutzerdefinierte ControllerFactory und überschreiben Sie 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
{ }

Schließen Sie die erstellte Controller-Factory in global.asax.cs An.

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

Jetzt können wir den Sitzungsstatus read-only Und read-write In einem einzigen Controller haben.

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);
    }
}

Hinweis: Der ASPNET-Sitzungsstatus kann auch im schreibgeschützten Modus noch beschrieben werden und löst keine Ausnahmebedingung aus (es wird nur nicht gesperrt, um die Konsistenz zu gewährleisten). Wir müssen daher darauf achten, AcquireSessionLock in den Aktionen des Controllers zu markieren das erfordert, Sitzungsstatus zu schreiben.

3
Misterhex

Das Markieren des Sitzungsstatus eines Controllers als schreibgeschützt oder deaktiviert löst das Problem.

Sie können einen Controller mit dem folgenden Attribut dekorieren, um ihn als schreibgeschützt zu kennzeichnen:

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

das System.Web.SessionState.SessionStateBehavior -Enum hat die folgenden Werte:

  • Default
  • Deaktiviert
  • Schreibgeschützt
  • Erforderlich
3
Michael King

Nur um jemandem bei diesem Problem zu helfen (Sperren von Anfragen, wenn eine andere aus derselben Sitzung ausgeführt wird) ...

Heute habe ich begonnen, dieses Problem zu lösen, und nach einigen Stunden der Recherche habe ich es gelöst, indem ich das Session_Start Methode (auch wenn leer) aus der Global.asax Datei.

Dies funktioniert in allen Projekten, die ich getestet habe.

0
graduenz