Właśnie odkryłem, dlaczego wszystkie ASP.Net strony internetowe są powolne, a ja staram się wymyślić, co z tym zrobić

Właśnie odkryłem, że każda prośba w ASP.Net aplikacja webowa otrzymuje blokadę sesji na początku żądania, a następnie zwalnia ją na końcu żądania!

W przypadku, gdy konsekwencje tego są stracone dla Ciebie, jak to było dla mnie na początku, oznacza to zasadniczo następujące:

  • Anytime an ASP.Net ładowanie strony zajmuje dużo czasu (może ze względu na powolne wywołanie bazy danych lub cokolwiek), a użytkownik decyduje, że chce przejść do innej strony, ponieważ są zmęczeni czekaniem, nie mogą! Na ASP.Net blokada sesji zmusza żądanie nowej strony do oczekiwania, aż pierwotne żądanie zakończy boleśnie wolne ładowanie. Arrrgh.

  • Za każdym razem, gdy panel UpdatePanel ładuje się powoli, a użytkownik decyduje się przejść do innej strony, zanim Panel UpdatePanel zakończy aktualizację... NIE MOGĄ! Na ASP.net blokada sesji zmusza żądanie nowej strony do oczekiwania, aż pierwotne żądanie zakończy boleśnie wolne ładowanie. Double Arrrgh!

Więc jakie są opcje? Do tej pory wymyśliłem:

  • zaimplementować niestandardową SessionStateDataStore, która ASP.Net podpory. Nie znalazłem zbyt wielu do skopiowania i wydaje się to ryzykowne i łatwe do namieszania.
  • Śledź wszystkie żądania w toku, a jeśli żądanie pochodzi od tego samego użytkownika, anuluj pierwotne żądanie. Wydaje się trochę ekstremalne, ale to działa(myślę).
  • nie używaj sesji! Kiedy potrzebuję jakiegoś stan dla użytkownika, mógłbym po prostu użyć pamięci podręcznej zamiast, i kluczowe elementy na uwierzytelnionej nazwy użytkownika, lub coś takiego. Znowu wydaje się trochę ekstremalne.

I really can ' t believe that the ASP.Net zespół Microsoft zostawiłby tak ogromne wąskie gardło wydajności w ramach w wersji 4.0! Przegapiłem coś oczywistego? Jak trudno byłoby użyć kolekcji ThreadSafe do sesji?

Author: qasimzee, 2010-09-02

8 answers

Jeśli strona nie modyfikuje żadnych zmiennych sesji, możesz zrezygnować z większości tej blokady.

<% @Page EnableSessionState="ReadOnly" %>

Jeśli Twoja strona nie odczytuje żadnych zmiennych sesji, możesz całkowicie zrezygnować z tej blokady dla tej strony.

<% @Page EnableSessionState="False" %>

Jeśli żadna ze stron nie używa zmiennych sesji, po prostu wyłącz stan sesji w sieci.config.

<sessionState mode="Off" />

Jestem ciekaw, jak myślisz, co zrobi "Kolekcja ThreadSafe", aby stać się bezpieczna dla wątków, jeśli nie używa zamków?

Edit: chyba powinienem wyjaśnij, co mam na myśli przez "opt out większości z tej blokady". Każda liczba stron sesji tylko do odczytu lub stron bez sesji może być przetwarzana dla danej sesji w tym samym czasie bez blokowania się nawzajem. Jednak strona sesji do odczytu i zapisu nie może rozpocząć przetwarzania, dopóki wszystkie żądania tylko do odczytu nie zostaną zakończone, a podczas jej działania musi mieć wyłączny dostęp do sesji danego użytkownika, aby zachować spójność. Blokowanie poszczególnych wartości nie zadziała, bo co jeśli jedna strona zmieni zestaw powiązane wartości jako grupa? Jak zapewnić, że inne strony działające w tym samym czasie otrzymają spójny widok zmiennych sesji użytkownika?

Sugerowałbym, aby spróbować zminimalizować modyfikowanie zmiennych sesji po ich ustawieniu, jeśli to możliwe. Umożliwiłoby to tworzenie większości stron sesji tylko do odczytu, zwiększając szansę, że wiele jednoczesnych żądań od tego samego użytkownika nie będzie blokować się nawzajem.

 189
Author: Joel Mueller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-02 18:27:16

OK, więc wielkie rekwizyty dla Joela Mullera za cały jego wkład. Moim ostatecznym rozwiązaniem było użycie niestandardowego modułu SessionStateModule opisanego na końcu tego artykułu MSDN:

Http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx{[4]

To było:

    Bardzo szybka implementacja (w rzeczywistości wydawała się łatwiejsza niż przejście przez dostawcę)
  • używałem wielu standardowych ASP.Net obsługa sesji po wyjęciu z pudełka (poprzez SessionStateUtility klasa)

To zrobiło ogromną różnicę w poczuciu "snapiness" do naszej aplikacji. Nadal nie mogę uwierzyć, że niestandardowe wdrożenie ASP.Net Session blokuje sesję dla całego żądania. To dodaje tak ogromną ilość powolności do stron internetowych. Sądząc po ilości badań online, które musiałem zrobić (i rozmowy z kilkoma naprawdę doświadczonymi ASP.Net deweloperów), Wiele osób doświadczyło tego problemu, ale bardzo niewiele osób kiedykolwiek dotarło do dna przyczyna. Może napiszę list do Scotta Gu...

Mam nadzieję, że to pomoże kilku osobom!

 76
Author: James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-07 18:58:18

Zacząłem używać AngiesList.Redis.RedisSessionStateModule , który oprócz korzystania z (bardzo szybkiego) serwera Redis do przechowywania (używam portu windows -- Chociaż istnieje również Port MSOpenTech), nie blokuje sesji.

Moim zdaniem, jeśli Twoja aplikacja jest skonstruowana w rozsądny sposób, to nie jest to problem. Jeśli rzeczywiście potrzebujesz zablokowanych, spójnych danych w ramach sesji, powinieneś w szczególności zaimplementuj sprawdzanie blokady / współbieżności na własną rękę.

MS zdecydował, że każdy ASP.NET sesja powinna być domyślnie zablokowana tylko po to, aby poradzić sobie ze złym projektem aplikacji, moim zdaniem jest to zła decyzja. Zwłaszcza, że wydaje się, że większość programistów nie zdawała sobie sprawy, że sesje były zablokowane, nie mówiąc już o tym, że aplikacje najwyraźniej muszą być ustrukturyzowane, aby można było zrobić stan sesji tylko do odczytu w jak największym stopniu (opt-out, gdzie to możliwe).

 31
Author: gregmac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-26 18:27:17

Przygotowałem bibliotekę opartą na linkach zamieszczonych w tym wątku. Wykorzystuje przykłady z MSDN i CodeProject. Dzięki Jamesowi.

Dokonałem też modyfikacji zalecanych przez Joela Muellera.

Kod jest tutaj:

Https://github.com/dermeister0/LockFreeSessionState

Moduł HashTable:

Install-Package Heavysoft.LockFreeSessionState.HashTable

Scaleeout StateServer module:

Install-Package Heavysoft.LockFreeSessionState.Soss

Moduł Niestandardowy:

Install-Package Heavysoft.LockFreeSessionState.Common

Jeśli chcesz wdrożyć wsparcie dla Memcached lub Redis, zainstaluj ten pakiet. Następnie Dziedzicz klasę LockFreeSessionStateModule i implementuj metody abstrakcyjne.

Kod nie jest jeszcze testowany na produkcji. Należy również poprawić obsługę błędów. Wyjątki nie są przechwytywane w bieżącej implementacji.

Niektórzy dostawcy sesji bez blokady używają Redis:

 17
Author: Der_Meister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 17:35:04

Chyba, że Twoja aplikacja ma specjalne potrzeby, myślę, że masz 2 podejścia:

  1. nie używaj w ogóle sesji
  2. Użyj sesji tak, jak jest i wykonaj precyzyjne dostrajanie, jak wspomniał joel.

Sesja jest nie tylko bezpieczna dla wątków, ale także bezpieczna dla stanu, w taki sposób, że wiesz, że dopóki bieżące żądanie nie zostanie zakończone, każda zmienna sesji nie zmieni się z innego aktywnego żądania. Aby tak się stało, musisz upewnić się, że sesja zostanie zablokowana dopóki aktualny wniosek został wypełniony.

Możesz utworzyć zachowanie podobne do sesji na wiele sposobów, ale jeśli nie zablokuje bieżącej sesji, nie będzie to "sesja".

Dla konkretnych problemów, o których wspomniałeś, myślę, że powinieneś sprawdzić HttpContext.Aktualne.Odpowiedź.IsClientConnected . Może to być przydatne, aby zapobiec niepotrzebnym egzekucjom i oczekiwaniom na kliencie, chociaż nie może całkowicie rozwiązać tego problemu, ponieważ może to być używane tylko przez sposób łączenia, a nie asynchroniczny.

 11
Author: George Mavritsakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-02 12:21:08

Dla ASPNET MVC wykonaliśmy następujące czynności:

  1. domyślnie ustawia SessionStateBehavior.ReadOnly wszystkie działania kontrolera przez nadpisanie DefaultControllerFactory
  2. w akcjach kontrolera, które wymagają zapisu do stanu sesji, zaznacz atrybutem, aby ustawić go na SessionStateBehaviour.Required

Utwórz własny ControllerFactory i nadpisaj 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
{ }

Podłącz utworzoną fabrykę kontrolerów w global.asax.cs

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

Teraz możemy mieć zarówno read-only jak i read-write stan sesji w jednym 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);
    }
}

Uwaga: stan sesji ASPNET może być zapisany nawet w readonly tryb i nie rzuci żadnej formy wyjątku (po prostu nie blokuje się do gwarantujemy spójność) dlatego musimy uważać, aby zaznaczyć AcquireSessionLock w akcjach kontrolera, które wymagają zapisu stanu sesji.

 2
Author: Misterhex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-09 08:47:46

Oznaczenie stanu sesji kontrolera jako readonlylub disabled rozwiąże problem.

Możesz ozdobić kontroler następującym atrybutem, aby oznaczyć go tylko do odczytu:

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

System .Www.SessionState.SessionStateBehavior enum ma następujące wartości:

  • Default
  • wyłączone
  • ReadOnly
  • wymagane
 2
Author: Michael King,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 23:05:38

Aby pomóc każdemu z tym problemem (blokowanie żądań podczas wykonywania innego z tej samej sesji)...

Dzisiaj zacząłem rozwiązywać ten problem i po kilku godzinach badań rozwiązałem go usuwając metodę Session_Start (nawet jeśli jest pusta) z Global.plik asax .

To działa we wszystkich testowanych przeze mnie projektach.

 0
Author: graduenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-30 19:19:07