Saya baru saja menemukan mengapa semua situs web ASP.Net lambat, dan saya mencoba mencari tahu apa yang harus dilakukan

275

Saya baru saja menemukan bahwa setiap permintaan dalam aplikasi web ASP.Net mendapat kunci Sesi di awal permintaan, dan kemudian melepaskannya di akhir permintaan!

Jika implikasi ini hilang pada Anda, seperti bagi saya pada awalnya, ini pada dasarnya berarti sebagai berikut:

  • Kapan saja situs web ASP.Net membutuhkan waktu lama untuk memuat (mungkin karena panggilan basis data yang lambat atau apa pun), dan pengguna memutuskan mereka ingin menavigasi ke halaman yang berbeda karena mereka lelah menunggu, MEREKA TIDAK BISA! Kunci sesi ASP.Net memaksa permintaan halaman baru untuk menunggu sampai permintaan asli selesai dengan lambat memuatnya. Arrrgh.

  • Kapan saja UpdatePanel memuat dengan lambat, dan pengguna memutuskan untuk menavigasi ke halaman yang berbeda sebelum UpdatePanel selesai memperbarui ... MEREKA TIDAK BISA! Kunci sesi ASP.net memaksa permintaan halaman baru untuk menunggu sampai permintaan asli selesai memuat dengan sangat lambat. Arrrgh Ganda!

Jadi apa saja pilihannya? Sejauh ini saya telah menemukan:

  • Menerapkan Custom SessionStateDataStore, yang didukung oleh ASP.Net. Saya belum menemukan terlalu banyak untuk disalin, dan sepertinya beresiko tinggi dan mudah kacau.
  • Pantau terus semua permintaan yang sedang berlangsung, dan jika ada permintaan dari pengguna yang sama, batalkan permintaan asli. Agak ekstrim, tapi itu akan berhasil (saya pikir).
  • Jangan gunakan Sesi! Ketika saya membutuhkan semacam status untuk pengguna, saya bisa menggunakan Cache saja, dan item kunci pada nama pengguna yang diautentikasi, atau semacamnya. Lagi-lagi sepertinya agak ekstrim.

Saya benar-benar tidak percaya bahwa tim Microsoft ASP.Net akan meninggalkan hambatan kinerja yang sangat besar dalam kerangka kerja pada versi 4.0! Apakah saya kehilangan sesuatu yang jelas? Seberapa sulitkah menggunakan koleksi ThreadSafe untuk Sesi?

James
sumber
40
Anda menyadari bahwa situs ini dibangun di atas .NET. Yang mengatakan, saya pikir itu cukup baik.
wheaties
7
OK, jadi saya sedikit jenaka dengan judul saya. Meski demikian, IMHO kinerja yang mengejutkan bahwa implementasi out of the box dari sesi memaksakan mengejutkan. Juga, saya yakin para Stack Overflow harus melakukan sedikit dev yang sangat khusus untuk mendapatkan kinerja dan skalabilitas yang telah mereka capai - dan pujian kepada mereka. Terakhir, Stack Overflow adalah aplikasi MVC, bukan WebForms, yang saya yakin bisa membantu (walaupun diakui ini masih menggunakan infrastruktur sesi yang sama).
James
4
Jika Joel Mueller memberi Anda informasi untuk memperbaiki masalah Anda, mengapa Anda tidak menandai jawabannya sebagai jawaban yang benar? Hanya pemikiran saja.
ars265
1
@ ars265 - Joel Muller memberikan banyak informasi yang baik, dan saya ingin berterima kasih padanya untuk itu. Namun, saya akhirnya pergi dengan rute yang berbeda dari yang disarankan di posnya. Karenanya, menandai pos yang berbeda sebagai jawabannya.
James

Jawaban:

201

Jika halaman Anda tidak mengubah variabel sesi apa pun, Anda dapat menyisih dari sebagian besar kunci ini.

<% @Page EnableSessionState="ReadOnly" %>

Jika halaman Anda tidak membaca variabel sesi apa pun, Anda dapat memilih keluar dari kunci ini sepenuhnya, untuk halaman itu.

<% @Page EnableSessionState="False" %>

Jika tidak ada halaman Anda menggunakan variabel sesi, matikan saja status sesi di web.config.

<sessionState mode="Off" />

Saya ingin tahu, apa yang Anda pikir "koleksi ThreadSafe" akan lakukan untuk menjadi thread-safe, jika tidak menggunakan kunci?

Sunting: Saya mungkin harus menjelaskan dengan apa yang saya maksud dengan "menyisih dari sebagian besar kunci ini". Sejumlah halaman read-only-session atau no-session dapat diproses untuk sesi yang diberikan secara bersamaan tanpa memblokir satu sama lain. Namun, halaman baca-tulis-sesi tidak dapat mulai memproses sampai semua permintaan read-only telah selesai, dan saat sedang berjalan itu harus memiliki akses eksklusif ke sesi pengguna itu untuk menjaga konsistensi. Mengunci nilai individual tidak akan berfungsi, karena bagaimana jika satu halaman mengubah satu set nilai terkait sebagai grup? Bagaimana Anda memastikan bahwa halaman lain yang berjalan pada saat yang sama akan mendapatkan tampilan yang konsisten dari variabel sesi pengguna?

Saya menyarankan agar Anda mencoba meminimalkan memodifikasi variabel sesi setelah mereka ditetapkan, jika memungkinkan. Ini akan memungkinkan Anda untuk membuat sebagian besar halaman Anda hanya baca-sesi-sesi, meningkatkan kemungkinan bahwa beberapa permintaan simultan dari pengguna yang sama tidak akan memblokir satu sama lain.

Joel Mueller
sumber
2
Hai Joel Terima kasih atas waktu Anda untuk jawaban ini. Ini adalah beberapa saran bagus dan beberapa makanan untuk dipikirkan. Saya tidak mengerti alasan Anda untuk mengatakan semua nilai untuk suatu sesi harus dikunci secara eksklusif di seluruh permintaan. Nilai-nilai Cache ASP.Net dapat diubah kapan saja oleh utas apa pun. Mengapa ini berbeda untuk sesi? Sebagai tambahan - satu masalah yang saya miliki dengan opsi readonly adalah bahwa jika seorang pengembang tidak menambahkan nilai pada sesi ketika mode readonly, itu diam-diam gagal (tidak terkecuali). Bahkan ia menyimpan nilai untuk sisa permintaan - tetapi tidak melampaui.
James
5
@ James - Saya hanya menebak motivasi para desainer di sini, tapi saya bayangkan lebih umum untuk memiliki beberapa nilai yang bergantung satu sama lain dalam satu sesi pengguna daripada dalam cache yang dapat dibersihkan karena kurangnya penggunaan atau rendah- alasan memori setiap saat. Jika satu halaman menetapkan 4 variabel sesi terkait, dan yang lain membacanya setelah hanya dua yang dimodifikasi, itu dapat dengan mudah menyebabkan beberapa bug yang sangat sulit didiagnosis. Saya membayangkan para perancang memilih untuk melihat "keadaan sesi pengguna saat ini" sebagai satu unit untuk tujuan penguncian karena alasan itu.
Joel Mueller
2
Jadi kembangkan sebuah sistem yang melayani programmer denominator umum terendah yang tidak bisa menguncinya? Apakah tujuan untuk mengaktifkan peternakan web yang berbagi sesi toko antara instance IIS? Bisakah Anda memberikan contoh sesuatu yang akan Anda simpan dalam variabel sesi? Saya tidak bisa memikirkan apa pun.
Jason Goemaat
2
Ya, inilah salah satu tujuannya. Pikirkan kembali berbagai skenario ketika load balancing dan redundansi diimplementasikan dalam infrastruktur. Ketika pengguna bekerja di halaman web, yaitu ia memasukkan data dalam formulir, karena, katakanlah, 5 menit, dan sesuatu di webfarm lumpuh - powersource dari satu simpul menjadi engah - pengguna TIDAK boleh memperhatikan itu. Dia tidak bisa dikeluarkan dari sesi hanya karena sesi itu hilang, hanya karena proses pekerja tidak ada lagi. Ini berarti bahwa untuk menangani keseimbangan / redundansi sempurna, sesi harus dieksternalisasi dari node pekerja ..
quetzalcoatl
7
Tingkat keikutsertaan lain yang bermanfaat adalah <pages enableSessionState="ReadOnly" />di web.config dan gunakan @Page untuk mengaktifkan penulisan pada halaman tertentu saja.
MattW
84

OK, Props begitu besar untuk Joel Muller untuk semua masukannya. Solusi utama saya adalah menggunakan Custom SessionStateModule yang dirinci di akhir artikel MSDN ini:

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

Ini:

  • Sangat cepat untuk diterapkan (sebenarnya tampak lebih mudah daripada pergi dengan rute penyedia)
  • Digunakan banyak sesi ASP.Net standar menangani di luar kotak (melalui kelas SessionStateUtility)

Ini telah membuat perbedaan besar pada perasaan "kekaburan" aplikasi kita. Saya masih tidak percaya implementasi kustom Sesi ASP.Net mengunci sesi untuk seluruh permintaan. Ini menambah jumlah kelesuan yang sangat besar ke situs web. Menilai dari jumlah riset online yang harus saya lakukan (dan percakapan dengan beberapa pengembang ASP.Net yang benar-benar berpengalaman), banyak orang telah mengalami masalah ini, tetapi sangat sedikit orang yang pernah sampai ke dasar penyebabnya. Mungkin saya akan menulis surat kepada Scott Gu ...

Saya harap ini membantu beberapa orang di luar sana!

James
sumber
19
Referensi itu adalah penemuan yang menarik, tetapi saya harus mengingatkan Anda tentang beberapa hal - kode sampel memiliki beberapa masalah: Pertama, ReaderWriterLocksudah tidak digunakan lagi ReaderWriterLockSlim- Anda harus menggunakannya. Kedua, lock (typeof(...))juga sudah usang - Anda harus mengunci bukan pada contoh objek statis pribadi. Ketiga, frasa "Aplikasi ini tidak mencegah permintaan Web simultan dari menggunakan pengidentifikasi sesi yang sama" adalah peringatan, bukan fitur.
Joel Mueller
3
Saya pikir Anda dapat membuat ini bekerja, tetapi Anda harus mengganti penggunaan SessionStateItemCollectiondalam kode sampel dengan kelas aman-benang (mungkin berdasarkan ConcurrentDictionary) jika Anda ingin menghindari kesalahan yang sulit untuk mereproduksi di bawah beban.
Joel Mueller
3
Saya hanya melihat ini sedikit lebih, dan sayangnya ISessionStateItemCollectionmembutuhkan Keysproperti menjadi tipe System.Collections.Specialized.NameObjectCollectionBase.KeysCollection- yang tidak memiliki konstruktor publik. Astaga, terima kasih kawan. Itu sangat nyaman.
Joel Mueller
2
OK, saya percaya saya akhirnya memiliki threadsafe penuh, implementasi penguncian membaca tidak berfungsi. Langkah-langkah terakhir melibatkan penerapan koleksi SessionStateItem threadsafe khusus, yang didasarkan pada artikel MDSN yang ditautkan dalam komentar di atas. Bagian terakhir dari teka-teki ini adalah membuat enumerator threadsafe berdasarkan artikel hebat ini: codeproject.com/KB/cs/safe_enumerable.aspx .
James
26
James - jelas ini adalah topik yang cukup lama, tetapi saya bertanya-tanya apakah Anda dapat berbagi solusi utama Anda? Saya sudah mencoba mengikuti dengan menggunakan utas komentar di atas tetapi sejauh ini belum bisa mendapatkan solusi yang berfungsi. Saya cukup yakin bahwa tidak ada yang mendasar dalam penggunaan sesi kami yang terbatas yang membutuhkan penguncian.
bsiegel
31

Saya mulai menggunakan AngiesList.Redis.RedisSessionStateModule , yang selain menggunakan server Redis (sangat cepat) untuk penyimpanan (saya menggunakan port windows - meskipun ada juga port MSOpenTech ), tidak ada penguncian pada sesi. .

Menurut pendapat saya, jika aplikasi Anda disusun dengan cara yang masuk akal, ini bukan masalah. Jika Anda benar-benar membutuhkan data yang terkunci dan konsisten sebagai bagian dari sesi, Anda harus secara khusus menerapkan pemeriksaan kunci / konkurensi sendiri.

MS memutuskan bahwa setiap sesi ASP.NET harus dikunci secara default hanya untuk menangani desain aplikasi yang buruk adalah keputusan yang buruk, menurut pendapat saya. Terutama karena sepertinya sebagian besar pengembang tidak / bahkan tidak menyadari sesi dikunci, apalagi bahwa aplikasi tampaknya perlu disusun sehingga Anda dapat melakukan status sesi baca-saja sebanyak mungkin (menyisih, jika mungkin) .

gregmac
sumber
Tautan GitHub Anda tampaknya sudah mati 404. libraries.io/github/angieslist/AL-Redis tampaknya menjadi URL baru?
Uwe Keim
Sepertinya penulis ingin menghapus perpustakaan, bahkan dari tautan kedua. Saya akan ragu untuk menggunakan perpustakaan yang ditinggalkan, tetapi ada garpu di sini: github.com/PrintFleet/AL-Redis dan perpustakaan alternatif yang ditautkan dari sini: stackoverflow.com/a/10979369/12534
Christian Davén
21

Saya menyiapkan perpustakaan berdasarkan tautan yang dipasang di utas ini. Ini menggunakan contoh-contoh dari MSDN dan CodeProject. Terima kasih untuk James.

Saya juga membuat modifikasi yang disarankan oleh Joel Mueller.

Kode ada di sini:

https://github.com/dermeister0/LockFreeSessionState

Modul HashTable:

Install-Package Heavysoft.LockFreeSessionState.HashTable

Modul ScaleOut StateServer:

Install-Package Heavysoft.LockFreeSessionState.Soss

Modul khusus:

Install-Package Heavysoft.LockFreeSessionState.Common

Jika Anda ingin menerapkan dukungan Memcached atau Redis, instal paket ini. Kemudian mewarisi kelas LockFreeSessionStateModule dan mengimplementasikan metode abstrak.

Kode belum diuji pada produksi. Juga perlu meningkatkan penanganan kesalahan. Pengecualian tidak ditangkap dalam implementasi saat ini.

Beberapa penyedia sesi tanpa kunci menggunakan Redis:

Der_Meister
sumber
Perlu perpustakaan dari solusi ScaleOut, yang tidak gratis?
Ho Long Long
1
Ya, saya membuat implementasi hanya untuk SOSS. Anda dapat menggunakan penyedia sesi Redis yang disebutkan, gratis.
Der_Meister
Mungkin Hoàng Long melewatkan poin bahwa Anda memiliki pilihan antara implementasi HashTable di memori dan ScaleOut StateServer.
David De Sloovere
Terima kasih atas kontribusi Anda :) Saya akan mencobanya untuk melihat bagaimana kerjanya pada beberapa kasus penggunaan yang kami miliki dengan SESI yang terlibat.
Agustin Garzon
Karena banyak orang telah menyebutkan mendapatkan kunci pada item tertentu dalam sesi, mungkin baik untuk menunjukkan bahwa implementasi dukungan perlu mengembalikan referensi umum ke nilai sesi di seluruh panggilan untuk mengaktifkan penguncian (dan bahkan itu tidak akan bekerja dengan server yang seimbang) Bergantung pada bagaimana Anda menggunakan status sesi, ada potensi kondisi lomba di sini. Juga, bagi saya tampaknya ada kunci dalam implementasi Anda yang tidak benar-benar melakukan apa-apa karena mereka hanya membungkus satu panggilan baca atau tulis (koreksi saya jika saya salah di sini).
nw.
11

Kecuali jika aplikasi Anda memiliki kebutuhan khusus, saya pikir Anda memiliki 2 pendekatan:

  1. Jangan gunakan sesi sama sekali
  2. Gunakan sesi apa adanya dan lakukan fine tuning seperti yang disebutkan joel.

Sesi tidak hanya thread-safe tetapi juga state-safe, dengan cara yang Anda tahu bahwa sampai permintaan saat ini selesai, setiap variabel sesi tidak akan berubah dari permintaan aktif lainnya. Agar hal ini terjadi, Anda harus memastikan bahwa sesi AKAN DIKUNCI sampai permintaan saat ini selesai.

Anda dapat membuat perilaku seperti sesi dengan banyak cara, tetapi jika itu tidak mengunci sesi saat ini, itu tidak akan menjadi 'sesi'.

Untuk masalah spesifik yang Anda sebutkan, saya pikir Anda harus memeriksa HttpContext.Current.Response.IsClientConnected . Ini dapat berguna untuk mencegah eksekusi dan menunggu yang tidak perlu pada klien, meskipun tidak dapat menyelesaikan masalah ini sepenuhnya, karena ini dapat digunakan hanya dengan cara penyatuan dan bukan asinkron.

George Mavritsakis
sumber
10

Jika Anda menggunakan pembaruan Microsoft.Web.RedisSessionStateProvider(mulai dari 3.0.2) Anda dapat menambahkan ini ke Anda web.configuntuk memungkinkan sesi bersamaan.

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

Sumber

rabz100
sumber
Tidak yakin mengapa ini di 0. +1. Sangat berguna.
Pangamma
Apakah ini berfungsi dalam kumpulan aplikasi mode klasik? github.com/Azure/aspnet-redis-providers/issues/123
Rusty
apakah ini berfungsi dengan penyedia layanan status inProc atau Session default?
Nick Chan Abdullah
Perhatikan bahwa referensi poster jika Anda menggunakan RedisSessionStateprovider, tetapi mungkin juga bekerja dengan penyedia Async AspNetSessionState yang lebih baru ini (untuk SQL dan Cosmos) karena juga dalam dokumentasi mereka: github.com/aspnet/AspNetSessionState Dugaan saya adalah bahwa itu akan bekerja di classicmode jika SessionStateProvider sudah bekerja dalam mode klasik, sepertinya hal-hal status sesi terjadi di dalam ASP.Net (bukan IIS). Dengan InProc itu mungkin tidak berfungsi tetapi akan lebih sedikit dari masalah karena itu memecahkan masalah pertentangan sumber daya yang merupakan masalah yang lebih besar dari skenario proc.
madamission
4

Untuk ASPNET MVC, kami melakukan hal berikut:

  1. Secara default, atur SessionStateBehavior.ReadOnlysemua tindakan pengontrol dengan mengesampingkanDefaultControllerFactory
  2. Pada tindakan pengontrol yang perlu menulis ke status sesi, tandai dengan atribut untuk mengaturnya SessionStateBehavior.Required

Buat kustom ControllerFactory dan ganti 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
{ }

Hubungkan pabrik pengontrol yang dibuat di global.asax.cs

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

Sekarang, kita dapat memiliki keduanya read-onlydan read-writestatus sesi dalam satu 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);
    }
}

Catatan: Status sesi ASPNET masih dapat ditulis bahkan dalam mode readonly dan tidak akan membuang segala bentuk pengecualian (Hanya saja tidak mengunci untuk menjamin konsistensi) jadi kita harus berhati-hati untuk menandai AcquireSessionLocktindakan pengontrol yang memerlukan status sesi penulisan.

Misterhex
sumber
3

Menandai status sesi pengontrol sebagai hanya baca atau dinonaktifkan akan menyelesaikan masalah.

Anda dapat menghias pengontrol dengan atribut berikut untuk menandainya hanya-baca:

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

yang System.Web.SessionState.SessionStateBehavior enum memiliki nilai-nilai berikut:

  • Default
  • Dengan disabilitas
  • Baca Saja
  • Yg dibutuhkan
Michael King
sumber
0

Hanya untuk membantu siapa saja dengan masalah ini (mengunci permintaan saat mengeksekusi yang lain dari sesi yang sama) ...

Hari ini saya mulai memecahkan masalah ini dan, setelah beberapa jam penelitian, saya menyelesaikannya dengan menghapus Session_Startmetode (bahkan jika kosong) dari file Global.asax .

Ini bekerja di semua proyek yang saya uji.

kelulusan
sumber
IDK apa jenis proyek ini, tetapi saya tidak memiliki Session_Startmetode dan masih terkunci
Denis G. Labrecque
0

Setelah berjuang dengan semua opsi yang tersedia, saya akhirnya menulis penyedia SessionStore berbasis token JWT (sesi berjalan di dalam cookie, dan tidak ada penyimpanan backend diperlukan).

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

Keuntungan:

  • Penggantian drop-in, tidak diperlukan perubahan pada kode Anda
  • Skala lebih baik daripada toko terpusat lainnya, karena tidak ada backend penyimpanan sesi yang diperlukan.
  • Lebih cepat dari penyimpanan sesi lainnya, karena tidak ada data yang perlu diambil dari penyimpanan sesi apa pun
  • Tidak memerlukan sumber daya server untuk penyimpanan sesi.
  • Implementasi non-blocking default: permintaan bersamaan tidak akan memblokir satu sama lain dan menahan kunci pada sesi
  • Skala aplikasi Anda secara horizontal: karena data sesi bepergian dengan permintaan itu sendiri, Anda dapat memiliki beberapa kepala web tanpa perlu khawatir berbagi sesi.
David
sumber