Właśnie odkryłem, że każde żądanie w aplikacji sieci web ASP.Net otrzymuje blokadę sesji na początku żądania, a następnie zwalnia je na końcu żądania!
W przypadku, gdy konsekwencje tego zostaną dla ciebie utracone, tak jak na początku dla mnie, oznacza to w zasadzie:
Za każdym razem, gdy strona ASP.Net ładuje się długo (być może z powodu powolnego wywołania bazy danych lub cokolwiek innego), a użytkownik decyduje, że chce przejść do innej strony, ponieważ ma dość czekania, NIE MOGĄ! Blokada sesji ASP.Net zmusza nowe żądanie strony do czekania, aż oryginalne żądanie zakończy się boleśnie powolnym ładowaniem. Arrrgh.
Za każdym razem, gdy UpdatePanel ładuje się powoli, a użytkownik decyduje się przejść do innej strony, zanim UpdatePanel zakończy aktualizację ... NIE MOGĄ! Blokada sesji ASP.net zmusza nowe żądanie strony do czekania, aż oryginalne żądanie zakończy się boleśnie powolnym ładowaniem. Double Arrrgh!
Jakie są więc opcje? Do tej pory wymyśliłem:
- Zaimplementuj niestandardowy SessionStateDataStore, który obsługuje ASP.Net. Nie znalazłem zbyt wielu do skopiowania i wydaje się to dość ryzykowne i łatwe do zepsucia.
- Śledź wszystkie żądania w toku, a jeśli żądanie pochodzi od tego samego użytkownika, anuluj pierwotne żądanie. Wydaje się to trochę ekstremalne, ale zadziałałoby (tak myślę).
- Nie używaj sesji! Kiedy potrzebuję jakiegoś stanu dla użytkownika, mógłbym po prostu użyć pamięci podręcznej zamiast tego i wprowadzić kluczowe elementy w uwierzytelnionej nazwie użytkownika lub coś w tym rodzaju. Znowu wydaje się to ekstremalne.
Naprawdę nie mogę uwierzyć, że zespół ASP.Net Microsoft pozostawiłby tak ogromne wąskie gardło w zakresie wydajności w wersji 4.0! Czy brakuje mi czegoś oczywistego? Jak trudno byłoby użyć kolekcji ThreadSafe do sesji?
Odpowiedzi:
Jeśli twoja strona nie modyfikuje żadnych zmiennych sesji, możesz zrezygnować z większości tej blokady.
Jeśli strona nie odczytuje żadnych zmiennych sesji, możesz całkowicie zrezygnować z tej blokady dla tej strony.
Jeśli żadna ze stron nie używa zmiennych sesji, po prostu wyłącz stan sesji w pliku web.config.
Ciekawe, co według ciebie zrobiłaby „kolekcja ThreadSafe”, aby stać się bezpieczną dla wątków, jeśli nie używa zamków?
Edycja: Prawdopodobnie powinienem wyjaśnić, co mam na myśli, mówiąc „zrezygnuj z większości tego zamka”. Dowolna liczba stron sesji tylko do odczytu lub stron bez sesji może być przetwarzana dla danej sesji jednocześnie bez wzajemnego blokowania. Jednak strona sesji odczytu-zapisu nie może rozpocząć przetwarzania, dopóki wszystkie żądania tylko do odczytu nie zostaną zakończone, a podczas działania musi mieć wyłączny dostęp do sesji tego użytkownika, aby zachować spójność. Blokowanie poszczególnych wartości nie działałoby, ponieważ co, jeśli jedna strona zmienia zestaw powiązanych wartości jako grupę? W jaki sposób zapewniłbyś, że inne strony działające w tym samym czasie uzyskałyby spójny widok zmiennych sesji użytkownika?
Sugeruję, abyś próbował zminimalizować modyfikowanie zmiennych sesji po ich ustawieniu, jeśli to możliwe. Pozwoliłoby to na uczynienie większości stron stronami sesji tylko do odczytu, zwiększając prawdopodobieństwo, że wiele jednoczesnych żądań od tego samego użytkownika nie blokuje się nawzajem.
źródło
<pages enableSessionState="ReadOnly" />
web.config i użyj @Page, aby umożliwić zapis tylko na określonych stronach.OK, wielkie rekwizyty dla Joela Mullera za cały jego wkład. Moim najlepszym 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
To było:
To ogromnie wpłynęło na poczucie „zgryźliwości” w naszej aplikacji. Nadal nie mogę uwierzyć, że niestandardowa implementacja sesji ASP.Net blokuje sesję dla całego żądania. To dodaje tak ogromnej ospałości stronom internetowym. Sądząc po ilości badań, które musiałem przeprowadzić (i rozmów z kilkoma naprawdę doświadczonymi programistami ASP.Net), wiele osób doświadczyło tego problemu, ale bardzo niewiele osób doszło do sedna sprawy. Może napiszę list do Scotta Gu ...
Mam nadzieję, że pomoże to kilku osobom!
źródło
ReaderWriterLock
jest przestarzały na korzyśćReaderWriterLockSlim
- powinieneś go użyć. Po drugie,lock (typeof(...))
również jest przestarzałe - zamiast tego należy zablokować prywatną instancję obiektu statycznego. Po trzecie, wyrażenie „Ta aplikacja nie uniemożliwia jednoczesnym żądaniom sieci Web korzystania z tego samego identyfikatora sesji” jest ostrzeżeniem, a nie funkcją.SessionStateItemCollection
w przykładowym kodzie klasą bezpieczną dla wątków (być może opartą naConcurrentDictionary
), jeśli chcesz uniknąć trudnych do odtworzenia błędów pod obciążeniem.ISessionStateItemCollection
wymaga, abyKeys
właściwość była typuSystem.Collections.Specialized.NameObjectCollectionBase.KeysCollection
- który nie ma publicznych konstruktorów. Rany, dzięki chłopaki. To bardzo wygodne.Zacząłem używać AngiesList.Redis.RedisSessionStateModule , który oprócz używania (bardzo szybkiego) serwera Redis do przechowywania (korzystam z portu systemu Windows - chociaż jest też port MSOpenTech ), absolutnie nie blokuje sesji .
Moim zdaniem, jeśli twoja aplikacja ma rozsądną strukturę, nie stanowi to problemu. Jeśli faktycznie potrzebujesz zablokowanych, spójnych danych w ramach sesji, powinieneś osobiście wdrożyć kontrolę blokady / współbieżności.
MS decydujące, że każda sesja ASP.NET powinna być domyślnie zablokowana tylko w celu obsługi złego projektu aplikacji, jest moim zdaniem złą decyzją. 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ą mieć taką strukturę, aby można było wykonywać stan sesji tylko do odczytu (w miarę możliwości). .
źródło
Przygotowałem bibliotekę na podstawie linków zamieszczonych w tym wątku. Wykorzystuje przykłady z MSDN i CodeProject. Dzięki Jamesowi.
Dokonałem również modyfikacji zalecanych przez Joela Muellera.
Kod jest tutaj:
https://github.com/dermeister0/LockFreeSessionState
Moduł HashTable:
Moduł ScaleOut StateServer:
Moduł niestandardowy:
Jeśli chcesz wdrożyć obsługę Memcached lub Redis, zainstaluj ten pakiet. Następnie odziedzicz klasę LockFreeSessionStateModule i zaimplementuj metody abstrakcyjne.
Niektórzy dostawcy sesji bez blokady korzystający z Redis:
źródło
Chyba że twoja aplikacja ma specjalne potrzeby, myślę, że masz 2 podejścia:
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 do czasu zakończenia bieżącego żądania.
Możesz utworzyć zachowanie podobne do sesji na wiele sposobów, ale jeśli nie zablokuje ono bieżącej sesji, nie będzie to „sesja”.
Dla konkretnych problemów, o których wspomniałeś, myślę, że powinieneś sprawdzić HttpContext.Current.Response.IsClientConnected . Może to być użyteczne, aby zapobiec niepotrzebnym wykonaniom i czeka na klienta, chociaż nie może całkowicie rozwiązać tego problemu, ponieważ można go użyć tylko w trybie puli, a nie asynchronizacji.
źródło
Jeśli używasz zaktualizowanego
Microsoft.Web.RedisSessionStateProvider
(od3.0.2
), możesz dodać to do swojego,web.config
aby umożliwić równoczesne sesje.Źródło
źródło
W przypadku ASPNET MVC wykonaliśmy następujące czynności:
SessionStateBehavior.ReadOnly
nadpisuj wszystkie działania kontroleraDefaultControllerFactory
SessionStateBehavior.Required
Utwórz niestandardowy obiekt ControllerFactory i zastąp
GetControllerSessionBehavior
.AcquireSessionLockAttribute
Podłącz utworzoną fabrykę kontrolerów w
global.asax.cs
Teraz możemy mieć zarówno stan sesji, jak
read-only
iread-write
sesję w jednymController
.źródło
Oznaczanie stanu sesji kontrolera jako tylko do odczytu lub wyłączone spowoduje rozwiązanie problemu.
Możesz ozdobić kontroler następującym atrybutem, aby oznaczyć go jako tylko do odczytu:
System.Web.SessionState.SessionStateBehavior enum ma następujące wartości:
źródło
Tylko po to, 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
Session_Start
metodę (nawet jeśli pustą) z Global.asax pliku .Działa to we wszystkich testowanych projektach.
źródło
Session_Start
metody i nadal się blokujePo zmaganiu się ze wszystkimi dostępnymi opcjami skończyłem pisać dostawcy SessionStore opartego na tokenach JWT (sesja podróżuje wewnątrz pliku cookie i nie jest potrzebne przechowywanie wewnętrznej bazy danych).
http://www.drupalonwindows.com/en/content/token-sessionstate
Zalety:
źródło