遇到這種問題,當然首先要先釐清到底在甚麼狀況下 Session 變數會消失?一開始大家猜測是 ASP.NET 1.1 和 ASP.NET 2.0 的 Session 有不同的運作機制(升級到 .NET 2.0 之後系統就掛了),這個大方向是對的,但最後發現問題的癥結其實跟 Session 的運作機制無關,Session 變數消失只是一個很糟糕的副作用罷了 …
經過反覆的測試,最後終於將範圍縮小至「呼叫一個 WebService (撈外部系統的資料)」後,便會造成 Session 變數消失。此時一位資深的同事注意到,由於外部資料可能常常會有變化,因此系統有一個特殊行為是會將 WebService 回傳的資料寫入一個暫存目錄中,而每次呼叫 WebService 前都會先將整個暫存目錄(及其中的檔案)刪除,而這就是造成 Session 變數消失的關鍵!(跟權控子系統一點關係都沒有)
經過一番 Google,發現這個 Session 變數會消失的問題最早大約在 2005 年就開始被討論,原因是從ASP.NET 2.0 開始新增的「Dynamic Compilation」機制,此玩意兒是指 ASP.NET (2.0+) Runtime 會持續監控應用程式的根目錄(Application Root Folder)以及其下所有的子目錄,出於安全性的考量,當這些目錄被更名或刪除時(不管是手動修改或是在 runtime 以程式修改),都會造成 Application Domain Restart!(也就是整個網站瞬間 shutdown、重新 compile 之後,再啟動)
當整個 w3wp process 都被砍掉重練了,Session 自然也不能倖免,直接就被刪除重建,其中的變數更是整個屍骨無存,最後導致 User 自動被登出系統。
在瞭解了基本觀念之後,又有資深的同事們提出了幾個更深入探討的方向,包括:
- 自ASP.NET 2.0後,網站可採取多種佈署方式 (ASP.NET Deployment Overview)
- ASP.NET 的 SessionStateMode 也有數種選擇(主要差異在於 scalability)
以下為測試結果:
SessionStateMode Deployment Style | InProc | StateServer | SQL Server |
Copy Web Site (直接將 Source Code 佈署出去) | X | ||
Pre-Compilation (for deployment) | X | ||
Pre-Compilation (for deployment and update) (等於執行Visual Studio 內建的 Publish WebSite) | X |
但是!事情並不是懶惰的我所想的那麼簡單!由以上的測試結果可明確的看出:選擇不同的 sessionStateMode 對 Session 變數會有截然不同的影響!在 sessionStateMode 採用「InProc Mode」時,只要更名(或刪除)網站的子目錄就會造成 Application Domain Restart(新增、刪除檔案則沒有這個副作用),若採用 StateServer/SQL Server 來儲存 Session 資料,則不管 Application Domain 如何反覆的砍掉重練,Session 變數還是可以堅忍不拔的活的很快樂!(其餘的 sessionState 屬性都採用預設值)
結論:
ASP.NET 2.0+ 的 Dynamic Compilation 行為是無法被改變的,若系統採用「InProc Mode」來儲存 Session 資料,且想要用程式在 runtime 清除暫存資料可以採取以下兩種作法:
- 以迴圈的方式刪除暫存目錄下的所有檔案,不要把暫存目錄整個刪除,此作法的effort 較小,亦可將暫存目錄設置於網站根目錄下一併管理。
- 將暫存目錄的位置移出網站的根目錄,如此便可任意更名/刪除此暫存目錄,缺點為管理上的 effort 較大(多一個虛擬目錄要管理)。
我不太懂為何 ASP.NET 會設計成,當採用 StateServer/SQLServer Mode 時就不清空 Session 資料。這樣就表示不同的 sessionStateMode 所代表的不僅僅是 Session 資料的儲存位置不同,連帶的 ASP.NET 對於 Session 資料的管理策略也是不同的,感覺對於開發人員來說這會是一個額外的負擔。
從效能的觀點來看,若系統中有某個功能一旦被執行就會造成 Application Domain Restart,那絕對不是甚麼好事。但是在開發階段只有採用「InProc Mode」或者已經事先深入瞭解 Dynamic Compilation 的運作機制,才能避免效能低落的程式寫法。若是經驗不足,或是不小心採用了 StateServer/SQLServer Mode,那麼系統只會默默的把 Application Domain 砍掉重練,不會丟出任何警告訊息,也不會寫下 Event Log,而Session 變數的內容看起來都會是很正常的!(權控子系統都可正常運作)那麼效能也只能默默的變差了 …
為了補足 ASP.NET 預設對於 Application Shutdown 事件不會寫 log 的缺憾,我在Microsoft Developer Division 的 VP - Scott Guthrie 的 blog 找到了一個方法,在Global.asax的「Application_End」事件中將Application Shutdown的資訊寫入到Event Log,如下圖所示,如此便可明確看出 Application Shutdown 的原因是因為「dir change or directory rename」:
Reference:
7 則留言:
喔喔喔 感謝大大解答我多年的疑惑
一直沒對這東西作比較深入的研究
難怪我之前的案子動不動就 session out
客戶一直抱怨這個 XDDD 不過最近好像又沒事了?
好文我頂 (三花聚頂
最近客戶又沒有跟你們抱怨就對了 ...
那應該是認命了吧 XDD 就跟股票認賠一樣 ..
"StateServer/SQLServer Mode 時就不清空 Session 資料" -> 會不會是考量到有多個 IIS Server 做 Cluster 共用 Session 時, 其中一個 Application Domain Restart 就清掉 Session 不太合理的問題 XD
Cluster 我就很不熟了@@ 但是印象中負責 Cluster 機制的軟體主要的任務就是記錄外來的 request 揪~~竟是交由內部的哪一台 Server 處理(類似 NAT?),因此若該 Server 上的 IIS 有 AP Domain Restart,應該把 Session 清掉也不會不合理? 每個 User 的 Session 應該還是被保存在內部的某一台 Server 上吧 .. ?
Anyway ... 我個人認為既然 AP Domain 都 Restart 了,不管採用何種 Session Mode,Session 還是一起死一死比較好 XD 這樣開發的時候才有機會檢視揪 ~~~ 竟是什麼原因導致 AP Domain Restart ~~~~
如果是 Development mode-> 同感,這樣的確可以方便找出問題
but 如果是 Production mode, Session 一起死一死會影響 User 的"奇摩子", 個人覺得這樣就不太好, 這個前提是環境是 IIS Cluster, SessionStateMod 是 State Server 或 SQL Server, 假如是InProc 的話就一起死一死吧 XD
感謝大大分享
剛好解決了我最近Session的問題
最終的作法,我是將會異動的資料夾移到IIS根目錄下,而非應用程式本身,就解決了這樣的問題。
我想,這樣也剛好可以避免掉一些檔案上傳的攻擊。不曉得我的見解有沒有誤~
Hi 鐵皮屋:
您說的「檔案上傳的攻擊」是說藉由上傳新的檔案,導致 session 被清空,造成 IIS 重建 session 的 loading / 使用者被登出,這樣的情境嗎?如果是這樣的話應該就可以避免,但如果是「利用上傳大量檔案來消耗 server 的資源」這種攻擊,應該是要額外實作一些偵測的機制來處理才行~
張貼留言