問題現象:
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。這個可以解決的,如果實際應用中需要此需求的人多(請回復評論),我會再整理分享出來。