IIS+Asp.Net Mvc必須知道的事(解決啟動/重啟/自動回收站點後第一次訪問慢問題)
問題現象:
Asp.net Mvc站點部署在IIS上後,第一個使用者第一次訪問站點,都會比較慢,確切的說是訪問站點的Action頁面(即非靜態頁面,因為靜態頁面直接由IIS處理返回給使用者即完成請求,而Action頁面IIS要轉交給Aspnet_Wp工作程序,進而涉及相關初始化操作,這些初始化操作是比較慢的。第二次訪問站點就不需要再初始化了所以就快了)。
這種第一次訪問慢的問題不僅發生在網站第一次部署啟動,也發生在站點重啟和站點程式池回收(經測試,第一次部署啟動初始化所用時間會多一些,然後是站點重啟,然後是站點回收)。
1.站點重啟包含手動重啟和修改web.config配置、修改IIS上站點配置、更新站點bin目錄的dll等引起的自動重啟。如果你的站點是新上線的web或者會持續修改新增功能的web,那難免會更新dll導致重啟。其它編譯型語言(比如java)也是如此,更新了服務端元件,都難免要重啟站點。這邊會分享.net環境下如何優化此問題;
2.站點程式池回收是IIS建議的,本來預設是29小時回收一次。為什麼要建議回收呢,大致可以這樣理解:一個每日定時回收的機制就像是在發生輕微記憶體洩露或者其它拖累Worker程序的因素的情況下,重新整理IIS的良藥,站點回收即節省了資源又提高了穩定性。然而,自動回收後第一次訪問慢的問題困擾了許多人,其實只要稍微設定就可以解決,即沒有困擾也擁有了回收的優點。
問題解決:
1.先在IIS上設定相應應用程式池的“高階設定”(IIS版本要在8或8以上,要知道IIS10早已出來了,如果你在用IIS很低的版本,然後在報怨IIS,我...),如下圖,這樣設定後,回收只會發生在凌晨04:00:00
要確定有安裝IIS應用程式初始化功能,如下圖
2.在IIS上設定站點的“高階設定”,把【預載入已啟用】設定為true。
設定完這兩步,當站點(自動)回收時,訪問站點也是秒開不受任何影響,它的原理是在回收時會保持站點持續執行,這樣的回收可以理解為把舊的Worker內容平滑的移到新的Worker上,然後回收掉舊的Worker。但是要注意,回收會導致站點記憶體資訊丟失,因此如果你的設計是把session放在記憶體,則就要設定永不自動回收,那隻要在第1步的基礎上把【特定時間】清空即可。不過我個人會建議你不要設計session放記憶體,你更新個dll導致站點重啟,記憶體也是清空的,你不如把session放在memcache/redis中,如果你的系統還沒用上這些,那你就用cookie代替session吧,cookie更靈活適用的場景也更多。
現在回收的問題完美解決了,接下來說說站點重啟。站點重啟肯定是要重新載入配置重新載入dll(不想重新載入dll的,看文章最後一段),初始化是免不了,預設重啟後第一個使用者第一次訪問站點會觸發初始化,那麼我們可以在站點啟動/重啟時,系統自動發一個站點請求,讓系統自己嚐嚐第一次訪問慢的問題。
IIS站點啟動時機自動請求站點
1.啟動站點時觸發的時機
建立一個類,繼承自IProcessHostPreloadClient介面,其Preload方法就是啟動站點時觸發。然後在裡面自動訪問站點,如下程式碼:
public class ApplicationPreload : System.Web.Hosting.IProcessHostPreloadClient { public void Preload(string[] parameters) { try { //自動請求的url,其中http://localhost:8001 最好配置在config中,這邊只是演示。 string url = "http://localhost:8001/home2/about"; using (var webClient = new WebClient()) { webClient.DownloadStringAsync(new Uri(url));//要非同步請求 } } catch (Exception e) { MvcApplication.DoLogToTxt("Preload Error:" + e.Message); } } }
2. 修改IIS配置檔案,讓IIS能識別到剛寫的ApplicationPreload類
開啟IIS配置檔案:%WINDIR%\System32\inetsrv\config\applicationHost.config
<applicationPools> <add name="MyAppWorkerProcess" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" /> <!-- 上面我們在IIS程式池介面中有設定過startMode項為AlwaysRunning--> </applicationPools> <!-- ... --> <sites> <site name="MySite" id="1"> <application path="/" serviceAutoStartEnabled="true" serviceAutoStartProvider="ApplicationPreload" /> </site> </sites> <serviceAutoStartProviders> <add name="ApplicationPreload" type="WebApplication1.ApplicationPreload, WebApplication1" /> </serviceAutoStartProviders>
最後一個條目的type,其中WebApplication1.ApplicationPreload是應用程式中實現IProcessHostPreloadClient介面的類的全名,WebApplication1是程式集名稱。
設定完這兩步也就搞定了啟動站點時自動訪問站點。
探討
在上面設定之前,我測試只更新站點部分dll,第一次訪問需要1~4秒,測試的站點是含有CMS原始碼的站點,不算小了。之前有個面試官說他們公司站點更新一個dll,第一次訪問需要10~30秒,甚至更久,這樣正在訪問站點的使用者就要等待,然後一直在報怨IIS和.Net,想要投奔java的懷抱(這裡不比較兩種語言,它們各自有自己的優勢),我問他是不是在Global.asax裡Application_Start做了太多自己的初始化,要麼有些初始化在使用者訪問到時處理負擔分擔出去,要麼Application_Start非同步處理初始化動作,但是他說這些自己的初始化都是使用者訪問前必須初始化好的。這...應該是自己系統設計不夠好不能怪.Net吧,如果是用了七七八八的第三方元件,比如EF初始化慢,那就換成輕量級的Dapper唄,然後好好學習一下《N種提升Asp.Net Mvc效能的方法》,不要閉門造車。
(你們更新一個站點的dll,第一次訪問需要多少秒呢?)
那麼,在上面設定之後 ,如果更新站點dll後第一次訪問需要1~4秒的情況下,系統自動幫你做了第一次訪問,很可能正在訪問的使用者就不怎麼察覺得出來站點有重啟過。如果是高併發訪問的大型網站,那就應該有負載均衡(IIS可以用NLB做負載均衡,或考慮CDN或DNS解析時就做負載均衡),應該有分散式等。
最後 ,如果你的站點是因為更新dll而導致站點重啟,而且對此問題深惡痛絕,那麼可以用.net的動態載入dll來解決,不是用AppDomain動態載入和解除安裝dll(這個太麻煩),而是用Assembly.LoadFile載入dll。比如Controller所在的dll是MvcA.dll,它引用了LibA.dll,當這兩個dll都有修改時,如何讓站點載入到這兩個最新版本的dll。
分享、互相交流學習