Tüm ASP.Net web sitelerinin neden yavaş olduğunu keşfettim ve bu konuda ne yapacağımı anlamaya çalışıyorum


275

Bir ASP.Net web uygulamasındaki her isteğin, bir isteğin başında bir Oturum kilidi aldığını ve isteğin sonunda bunu serbest bıraktığını keşfettim!

Bunun etkileri sizin için kaybolursa, ilk başta benim için olduğu gibi, bu temelde aşağıdakiler anlamına gelir:

  • Bir ASP.Net web sayfasının yüklenmesi uzun zaman alıyor olabilir (belki de yavaş bir veritabanı çağrısı veya herhangi bir şeyden dolayı) ve kullanıcı beklemekten bıktıkları için farklı bir sayfaya gitmek istediklerine karar verir, YAPAMAZ! ASP.Net oturum kilidi yeni sayfa isteğini, özgün istek acı yavaş yavaş bitene kadar beklemeye zorlar. Arrrgh.

  • Bir UpdatePanel yavaş yüklendiğinde ve kullanıcı UpdatePanel'in güncellenmesi bitmeden önce farklı bir sayfaya gitmeye karar verir ... YAPAMAZ! ASP.net oturum kilidi yeni sayfa isteğini özgün isteği acı yavaş yavaş bitene kadar beklemeye zorlar. Çift Arrrgh!

Peki seçenekler nelerdir? Şimdiye kadar buldum:

  • ASP.Net'in desteklediği bir Özel SessionStateDataStore uygulayın. Kopyalamak için çok fazla şey bulamadım ve yüksek riskli ve dağılması kolay görünüyor.
  • Devam eden tüm istekleri takip edin ve aynı kullanıcıdan bir istek gelirse, orijinal isteği iptal edin. Bir tür aşırı gibi görünüyor, ama işe yarayacaktı (sanırım).
  • Oturum'u kullanma! Kullanıcı için bir tür duruma ihtiyacım olduğunda, bunun yerine Önbellek'i ve kimliği doğrulanmış kullanıcı adındaki anahtar öğeleri veya böyle bir şeyi kullanabilirim. Yine aşırı gibi görünüyor.

ASP.Net Microsoft ekibinin 4.0 sürümünde bu kadar büyük bir performans darboğazı bırakacağına gerçekten inanamıyorum! Açık bir şey mi kaçırıyorum? Oturum için ThreadSafe koleksiyonu kullanmak ne kadar zor olurdu?


40
Bu sitenin .NET üstüne inşa edildiğinin farkındasınız. Bununla birlikte, oldukça güzel ölçeklendiğini söyledi.
wheaties

7
Tamam, bu yüzden unvanımla biraz müstehcen oluyordum. Yine de IMHO, oturumun kutudan çıktığı uygulamanın uyguladığı performans şok edici şaşırtıcı. Ayrıca, Stack Overflow'ların elde ettikleri performans ve ölçeklenebilirliği ve onlara kudosları elde etmek için oldukça özel bir şey yapmak zorunda kaldıklarıma bahse girerim. Son olarak, Stack Overflow, bir MVC APP, WebForms değil, bahse girerim (kuşkusuz bu hala aynı oturum altyapısını kullanmasına rağmen).
James


4
Joel Mueller size sorununuzu çözmek için bilgi verdiyse, neden cevabını doğru cevap olarak işaretlemediniz? Sadece bir düşünce.
ars265 26:12

1
@ ars265 - Joel Muller birçok iyi bilgi verdi ve bunun için ona teşekkür etmek istedim. Ancak nihayetinde görevinde önerilenden farklı bir rota izledim. Bu nedenle, farklı bir gönderiyi yanıt olarak işaretlemek.
James

Yanıtlar:


201

Sayfanızda hiçbir oturum değişkeni değişmezse, bu kilidin çoğunu devre dışı bırakabilirsiniz.

<% @Page EnableSessionState="ReadOnly" %>

Sayfanızda herhangi bir oturum değişkeni okunmuyorsa, söz konusu sayfa için bu kilidi tamamen devre dışı bırakabilirsiniz.

<% @Page EnableSessionState="False" %>

Sayfalarınızdan hiçbiri oturum değişkeni kullanmıyorsa, web.config dosyasındaki oturum durumunu kapatmanız yeterlidir.

<sessionState mode="Off" />

Merak ediyorum, kilit kullanmazsa "ThreadSafe koleksiyonu" iş parçacığı için güvenli hale getirmek için ne yapar?

Düzenleme: Muhtemelen ne demek istediğimi açıklamak gerekir "bu kilit çoğu devre dışı". Herhangi bir sayıda salt okunur veya oturum olmayan sayfa belirli bir oturum için birbirini engellemeden aynı anda işlenebilir. Ancak, bir salt okunur oturum sayfası tüm salt okunur istekler tamamlanana kadar işleme başlayamaz ve çalışırken tutarlılığı sağlamak için kullanıcının oturumu için özel erişime sahip olması gerekir. Bireysel değerler üzerinde kilitleme işe yaramaz, çünkü bir sayfa bir grup olarak ilgili değerler grubunu değiştirirse? Aynı anda çalışan diğer sayfaların kullanıcının oturum değişkenlerini tutarlı bir şekilde görmesini nasıl sağlarız?

Mümkünse, oturum değişkenleri ayarlandıktan sonra değiştirilmesini en aza indirmeye çalışmanızı öneririm. Bu, sayfalarınızın çoğunu salt okunur sayfalar yapmanızı sağlayarak aynı kullanıcıdan gelen birden fazla eşzamanlı isteğin birbirini engellememe şansını artırır.


2
Merhaba Joel Bu yanıta zaman ayırdığınız için teşekkür ederiz. Bunlar bazı iyi öneriler ve düşünce için yiyecek. Bir oturum için tüm değerlerin yalnızca tüm istek boyunca kilitlenmesi gerektiğini söyleme nedeninizi anlamıyorum. ASP.Net Önbellek değerleri herhangi bir zamanda herhangi bir iş parçacığı tarafından değiştirilebilir. Bu oturum için neden farklı olmalı? Bir yana - ben salt okunur seçeneği ile bir sorun var bir geliştirici salt okunur modda oturuma bir değer eklerse, sessizce başarısız (istisna) olmasıdır. Aslında, talebin geri kalanı için değeri tutar - ancak ötesinde değil.
James

5
@James - Buradaki tasarımcıların motivasyonlarını tahmin ediyorum, ancak tek bir kullanıcının oturumunda birden fazla değerin birbirine bağlı olmamasının, kullanım eksikliği veya düşük olması nedeniyle temizlenebilen bir önbellekten daha yaygın olduğunu düşünüyorum. herhangi bir zamanda hafıza nedenleri. Bir sayfa 4 ilgili oturum değişkeni ayarlarsa ve diğeri yalnızca iki değiştirildikten sonra bunları okursa, bu çok kolay teşhis edilmesi zor hatalara yol açabilir. Tasarımcıların, "kullanıcı oturumunun mevcut durumunu" bu nedenle kilitleme amacıyla tek bir birim olarak görmeyi seçtiğini düşünüyorum.
Joel Mueller

2
Peki, kilitlemeyi anlayamayan en düşük ortak payda programcılarına hitap eden bir sistem geliştirin mi? Bir oturum deposunu IIS örnekleri arasında paylaşan web çiftliklerini etkinleştirmenin amacı var mı? Bir oturum değişkeninde saklayacağınız bir şeye örnek verebilir misiniz? Hiçbir şey düşünemiyorum.
Jason Goemaat

2
Evet, amaçlardan biri bu. Altyapıya yük dengeleme ve artıklık uygulandığında çeşitli senaryoları yeniden düşünün. Kullanıcı web sayfasında çalıştığında, yani 5 dakika boyunca formda veri giriyorsa ve web çiftliğinde bir şey çöktüğünde - bir düğümün güç kaynağı puf gider - kullanıcı bunu fark etmemelidir. Sadece oturumu kaybolduğu için oturumdan atılamaz, sadece işçi süreci artık mevcut olmadığından. Bu, mükemmel dengeleme / fazlalık ile başa çıkmak için, oturumların çalışan düğümlerden harici hale getirilmesi gerektiği anlamına gelir.
quetzalcoatl

6
Diğer bir kullanışlı çıkış düzeyi de <pages enableSessionState="ReadOnly" />web.config dosyasındadır ve @Page işlevini kullanarak yalnızca belirli sayfalarda yazmayı etkinleştirir.
MattW

84

Tamam, Joel Muller'a tüm girdileri için çok büyük bir sahne. Nihai çözüm, bu MSDN makalesinin sonunda ayrıntılı olarak açıklanan Custom SessionStateModule kullanmaktı:

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

Buydu:

  • Uygulaması çok hızlı (aslında sağlayıcı rotasına gitmekten daha kolay görünüyordu)
  • Kutudan çıkar çıkmaz standart ASP.Net oturumunun çoğunu kullandı (SessionStateUtility sınıfı aracılığıyla)

Bu bizim uygulama "snapiness" hissi BÜYÜK bir fark yarattı. ASP.Net oturumunun özel uygulamasının tüm istek için oturumu kilitlediğine hala inanamıyorum. Bu, web sitelerine bu kadar büyük bir halsizlik ekler. Yapmam gereken çevrimiçi araştırma miktarından (ve gerçekten deneyimli birkaç ASP.Net geliştiricisiyle yapılan görüşmelerden) bakıldığında, birçok insan bu sorunu yaşadı, ancak çok az insan nedenin sonuna geldi. Belki Scott Gu'ya bir mektup yazacağım ...

Umarım bu birkaç kişiye yardımcı olur!


19
Bu referans ilginç bir bulgu, ancak birkaç şey hakkında sizi uyarmalıyım - örnek kodun bazı sorunları var: İlk ReaderWriterLockolarak, lehine kullanımdan kaldırıldı ReaderWriterLockSlim- bunun yerine bunu kullanmalısınız. İkincisi, lock (typeof(...))kullanımdan kaldırıldı - bunun yerine özel bir statik nesne örneğine kilitlemelisiniz. Üçüncü olarak, "Bu uygulama eşzamanlı Web isteklerinin aynı oturum tanımlayıcısını kullanmasını engellemez" ifadesi bir özellik değil, bir uyarıdır.
Joel Mueller

3
Bu işi yapabilirsiniz düşünüyorum, ancak yük altında çoğaltmak zor hataları önlemek istiyorsanız SessionStateItemCollectionörnek kod kullanımı (belki de dayalı ConcurrentDictionary) bir iş parçacığı güvenli sınıf ile değiştirmeniz gerekir .
Joel Mueller

3
Biraz daha bu içine baktı ve ne yazık ki ISessionStateItemCollectiongerektiren Keystürde olması özelliğini System.Collections.Specialized.NameObjectCollectionBase.KeysCollection- hiçbir kamu oluşturucusu yok. Gee, teşekkürler çocuklar. Bu çok uygun.
Joel Mueller

2
Tamam, nihayet Oturum çalışma tam bir threadsafe, okuma olmayan kilitleme uygulaması var inanıyorum. Son adımlar, yukarıdaki yorumda bağlantılı olan MDSN makalesine dayanan özel bir threadsafe SessionStateItem koleksiyonunun uygulanmasını içeriyordu. Bu bulmacanın son parçası, bu harika makaleye dayanan bir threadsafe numaralandırıcı oluşturmaktı: codeproject.com/KB/cs/safe_enumerable.aspx .
James

26
James - açıkçası bu oldukça eski bir konu, ama nihai çözümünüzü paylaşıp paylaşamayacağınızı merak ediyordum? Yukarıdaki yorum dizisini kullanarak takip etmeye çalıştım ama şimdiye kadar çalışan bir çözüm elde edemedik. Oturumu sınırlı kullanmamızın kilitlenmesi gereken temel bir şey olmadığından oldukça eminim.
bsiegel

31

(Çok hızlı) Redis sunucusunu kullanmanın yanı sıra AngiesList.Redis.RedisSessionStateModule'u kullanmaya başladım depolama için (ben kullanıyorum pencereler portu - de bulunduğu olsa MSOpenTech portu , kesinlikle hiçbir oturumuna kilitleme etmez) .

Kanımca, başvurunuz makul bir şekilde yapılandırılmışsa, bu bir sorun değildir. Oturumun bir parçası olarak gerçekten kilitli ve tutarlı verilere ihtiyacınız varsa, özellikle kendi başınıza bir kilit / eşzamanlılık kontrolü uygulamanız gerekir.

MS, sadece kötü uygulama tasarımını işlemek için her ASP.NET oturumunun varsayılan olarak kilitlenmesi gerektiğine karar vermek benim görüşüme göre kötü bir karardır. Özellikle, çoğu geliştirici oturumların kilitli olduğunu bile bilmiyor / bilmiyor gibi göründüğü için, uygulamaların yapılandırılmış olması gerektiği gibi, salt okunur oturum durumunu olabildiğince yapabilmeniz için (mümkün olduğu durumlarda devre dışı bırakma) .


GitHub bağlantınız 404 ölü gibi görünüyor. libraries.io/github/angieslist/AL-Redis yeni URL gibi görünüyor mu?
Uwe Keim

Görünüşe göre yazar ikinci bağlantıdan bile kütüphaneyi kaldırmak istedi. Terk edilmiş bir kütüphane kullanmakta tereddüt ederdim, ama burada bir çatal var: github.com/PrintFleet/AL-Redis ve buradan bağlı alternatif bir kütüphane: stackoverflow.com/a/10979369/12534
Christian Davén

21

Bu konuya gönderilen bağlantıları temel alan bir kütüphane hazırladım. MSDN ve CodeProject örneklerini kullanır. James'e teşekkürler.

Ayrıca Joel Mueller tarafından tavsiye edilen değişiklikler yaptım.

Kod burada:

https://github.com/dermeister0/LockFreeSessionState

HashTable modülü:

Install-Package Heavysoft.LockFreeSessionState.HashTable

ScaleOut StateServer modülü:

Install-Package Heavysoft.LockFreeSessionState.Soss

Özel modül:

Install-Package Heavysoft.LockFreeSessionState.Common

Memcached veya Redis desteğini uygulamak istiyorsanız, bu paketi yükleyin. Ardından LockFreeSessionStateModule sınıfını devralın ve soyut yöntemleri uygulayın.

Kod henüz üretimde test edilmemiştir. Ayrıca hata işlemeyi geliştirmeniz gerekir. Mevcut uygulamada istisnalar yakalanmaz.

Redis kullanan bazı kilitsiz oturum sağlayıcıları:


Ücretsiz olmayan ScaleOut çözümü kütüphaneleri gerektirir?
Hoàng Long

1
Evet, yalnızca SOSS için uygulama oluşturdum. Bahsedilen Redis oturum sağlayıcılarını kullanabilirsiniz, ücretsizdir.
Der_Meister

Belki de Hoàng Long, bellek içi HashTable uygulaması ile ScaleOut StateServer arasında seçim yapabileceğiniz noktayı kaçırdı.
David De Sloovere

Katkınız için teşekkürler :) SESSION ile ilgili birkaç kullanım vakasına nasıl etki ettiğini görmeye çalışacağım.
Agustin Garzon

Birçok kişi oturumdaki belirli bir öğeye kilitlendiğinden bahsettiğinden, destekleme uygulamasının kilitlemeyi etkinleştirmek (ve hatta yük dengeli sunucularla çalışmaz). Oturum durumunu nasıl kullandığınıza bağlı olarak, burada yarış koşulları potansiyeli vardır. Ayrıca, uygulamada aslında hiçbir şey yapmayan kilitler olduğu anlaşılıyor çünkü sadece tek bir okuma veya yazma çağrısını sarıyorlar (burada yanılıyorsam beni düzelt).
nw.

11

Başvurunuzun özel olarak ihtiyacı olmadıkça, 2 yaklaşımınızın olduğunu düşünüyorum:

  1. Oturumu hiç kullanma
  2. Oturumu olduğu gibi kullanın ve joel'den bahsedildiği gibi ince ayar yapın.

Oturum, yalnızca iş parçacığı için güvenli değil aynı zamanda devlet için de güvenlidir, geçerli istek tamamlanana kadar her oturum değişkeninin başka bir etkin istekten değişmeyeceğini bildiğiniz şekilde. Bunun gerçekleşmesi için , mevcut istek tamamlanana kadar oturumun KİLİTLENECEĞİNDEN emin olmalısınız .

Davranış gibi bir oturum birçok yolla oluşturabilirsiniz, ancak geçerli oturumu kilitlemezse, 'oturum' olmaz.

Bahsettiğiniz özel sorunlar için HttpContext.Current.Response.IsClientConnected'ı kontrol etmeniz gerektiğini düşünüyorum . Bu, istemcide gereksiz yürütmeleri ve beklemeleri önlemek için yararlı olabilir, ancak bu sorunu tamamen çözemese de, bu zaman uyumsuz değil yalnızca bir havuzlama yolu ile kullanılabilir.


10

Güncellenmiş Microsoft.Web.RedisSessionStateProvider(başlangıçtan itibaren 3.0.2) kullanıyorsanız, web.configeşzamanlı oturumlara izin vermek için bunu adresinize ekleyebilirsiniz .

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

Kaynak


Bunun neden 0'da olduğundan emin değilim. +1. Çok kullanışlı.
Pangamma

Klasik mod uygulama havuzunda çalışıyor mu? github.com/Azure/aspnet-redis-providers/issues/123
Paslı

bu varsayılan inProc veya Session state servis sağlayıcısı ile çalışıyor mu?
Nick Chan Abdullah

RedisSessionStateprovider kullanıyorsanız poster referanslarına dikkat edin, ancak bu daha yeni AspNetSessionState Async sağlayıcılarıyla (SQL ve Cosmos için) de çalışabilir, çünkü belgelerinde de bulunmaktadır: github.com/aspnet/AspNetSessionState Tahminimde SessionStateProvider zaten klasik modda çalışıyorsa, büyük olasılıkla oturum durumları ASP.Net (IIS değil) içinde gerçekleşir. InProc ile işe yaramayabilir, ancak daha az sorun olacaktır çünkü proc senaryoları ile daha büyük bir anlaşma olan bir kaynak çekişmesi sorununu çözer.
hanımefendi

4

ASPNET MVC için aşağıdakileri yaptık:

  1. Varsayılan olarak, SessionStateBehavior.ReadOnlytüm denetleyicinin eylemini geçersiz kılarak ayarlayınDefaultControllerFactory
  2. Oturum durumuna yazılması gereken denetleyici eylemlerinde, SessionStateBehavior.Required

Özel ControllerFactory oluşturun ve geçersiz kılın GetControllerSessionBehavior.

    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
{ }

Oluşturulan denetleyici fabrikasını bağlayın global.asax.cs

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

Şimdi, her ikisine de sahip olabilir read-onlyve read-writetek oturum durumunu 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);
    }
}

Not: ASPNET oturum durumu yine de salt okunur modda yazılabilir ve herhangi bir istisna oluşturmaz (Tutarlılığı garanti etmek için kilitlenmez), bu nedenle AcquireSessionLockdenetleyicinin oturum durumunu yazma gerektiren eylemlerini işaretlememize dikkat etmeliyiz.



3

Bir denetleyicinin oturum durumunu salt okunur veya devre dışı olarak işaretlemek sorunu çözecektir.

Bir denetleyiciyi salt okunur olarak işaretlemek için aşağıdaki öznitelikle dekore edebilirsiniz:

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

System.Web.SessionState.SessionStateBehavior enum aşağıdaki değerlere sahiptir:

  • Varsayılan
  • engelli
  • Sadece oku
  • gereklidir

0

Sadece bu sorunu olan herkese yardımcı olmak için (aynı oturumdan başka bir tane yürütürken istekleri kilitleme) ...

Bugün bu sorunu çözmeye başladım ve birkaç saatlik araştırmadan sonra, Session_Startyöntemi (boş olsa bile) Global.asax dosyasından kaldırarak çözdüm .

Bu, test ettiğim tüm projelerde işe yarar.


IDK bunun ne tür bir projede olduğunu, ancak benimkinin bir Session_Startyöntemi yok ve hala kilitleniyor
Denis G. Labrecque

0

Mevcut tüm seçeneklerle uğraştıktan sonra, JWT jeton tabanlı bir SessionStore sağlayıcısı yazdım (oturum bir çerez içinde dolaşıyor ve arka uç depolamasına gerek yok).

http://www.drupalonwindows.com/en/content/token-sessionstate

Avantajları:

  • Drop-in değiştirme, kodunuzda değişiklik gerekmez
  • Oturum depolama arka ucuna gerek olmadığından, diğer tüm merkezi mağazalardan daha iyi ölçeklendirin.
  • Herhangi bir oturum deposundan veri alınması gerekmediğinden, diğer oturum depolarından daha hızlı
  • Oturum depolama için hiçbir sunucu kaynağı tüketmez.
  • Varsayılan engellemeyen uygulama: eşzamanlı istek birbirini engellemez ve oturumda bir kilit tutmaz
  • Uygulamanızı yatay olarak ölçeklendirin: oturum verileri isteğin kendisi ile seyahat ettiğinden, oturum paylaşımı konusunda endişelenmeden birden çok web başlığına sahip olabilirsiniz.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.