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