最近項目在使用EF了,mvc使用EF确實友善,因為添加功能的時候可以使用vs自動生成用ef的增、删、查、改的模闆,大的提高的工作效率。但是很多人都遇到過用EF開發的程式在第一次通路的時候會比用ADO純sql慢很多,過一段時間不通路又會變慢。我最近的兩個項目分别是藍狐軟體工作室和一個商城系統都是用MVC5+EF6Code First開發的,都遇到過這樣的問題。下面我就分享一下我們藍狐在這個優化的過程中使用的解決辦法。
問題描述:第一次通路的時候很慢,後面再次打開頁面很快,過了一段時間不通路頁面然後再次打開頁面又像第一次那樣很慢。
采用的技術和環境:
windows 2008 64位+IIS7.5
vs2013+mvc5
entity framework6 Code First
我使用MiniProfiler.EF來監控來診斷到底是什麼導緻頁面第一次通路為什麼這麼慢。監控到的結果如下圖:
可以看到這個頁面第一次通路總共花了37643毫秒,也就是37.6秒,這樣大的影響了使用者體驗,讓人無法忍受。有會人說.net的程式第一次本身就很慢,但是這也太慢了。提升ASP.NET的程式性能解決方案有很多,比如解決第一次通路就可以預編譯代碼,但是這個不屬于本文的讨論範疇,本文主要讨論EF和程式池初始化慢的問題。我們第一想到的是不是EF導緻的太慢,按理說EF已經是6了不會性能這麼差吧,而且sql部分隻占1.6%的時間,也就是597.8毫秒的時間。
藍狐軟體工作室通過了一些優化方法,終于把這個”第一次通路慢,再打開其它頁面就很快,隔一段時間不通路再通路又變慢的問題“解決了。優化之後效果有了比較大的提升。停止應用程式池之後的第一次通路結果:
再次通路結果:
隔很久不通路再次通路頁面響應時間也能保持4-8秒内
第一、問題原因分析
Ø EF方面的原因:
1、Code First第一次啟動會對比程式中的Model與資料庫表(database initializer ),生成Model與資料庫的映射視圖
2、随着EF的開源,EF從6開始就不會包含在.net Framework中,安裝.net Framework預設是不會安裝EF的。是以EF程式集就沒有生成本地鏡像,這樣每次程式啟動,EF的代碼都會通過just-in-time (JIT) compiler(即時編譯器)把MSIL中間代碼編譯成本機能識别的本地代碼。因為這個生成的本地代碼存在程式運作的程序裡面的記憶體中,它将回收當程式程序被終止(例如:iis程式池回收,程式池預設是按需觸發運作的,沒人通路它就不啟動了)。由于EF架構還是比較大的,EF6檔案大小到4-5M了,是以每次啟動都要重寫編譯本地代碼有比較明顯的性能影響。
Ø 抛開EF架構程式啟動慢的問題主要有以下兩方面的原因:
1、站點更新後重新加載程式檔案;
2、iis程式池回收後也會需要重新加載(程式池預設是按需觸發運作的,沒人通路它就不啟動了)
MVC的程式第一次通路比較慢的的問題由于第一次是要處理視圖檔案.cshtml(生成為.cs檔案)、加載引用的dll程式檔案和初始化程式池等等。
第二、優化方案
我主要是通過以下幾方面來優化
一、安裝Application Initialization
這是在iis8出來後才有的,iis8内置的功能,而對于iis7.5也提供了一個擴充以支援這個功能。
Application Initialization Module for IIS 7.5
在頁面接近底部的地方,找到适合自己架構的安裝連結
x86 for Windows 7
x64 for Windows 7 or Windows Server 2008 R2
安裝這個iis子產品後,在iis界面中并沒有子產品圖示和配置界面,還需要安裝:
http://pan.baidu.com/s/1c091WxM
安裝成功之後會多了一個配置如下圖:
如果僅配置程式池StartMode為AlwaysRunning還不放心的話,
也可以同時針對站點開啟preload和DoAppInitAfterRestart。
設定應用程式池如下圖:
設定網站如下圖
配置好後,測試了下,效果十分不錯。
回收程式池後首次打開各站點,延遲都很低。
其實這個子產品的思路和定時從外部觸發一個通路是一樣的,隻是,更好的地方在于,它本身在程式池回收重新開機的時候就完成了這件事,而不會讓外部通路有機會遇到首次通路的情況。
二、用Ngen安裝生成EF的本地鏡像
1、打開cmd視窗
2、定位到dll所在的目錄,如:cd d:\website1\bin,切換到程式的bin目錄。
3、運作ngen指令
For 32 bit run:
%WINDIR%\Microsoft.NET\Framework\v4.0.30319\ngen install EntityFramework.SqlServer.dll
For 64 bit run:
%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\ngeninstall EntityFramework.SqlServer.dll
注意:這裡根據你自己機器(是32還是64)和.net版本,選擇相應的指令,隻需要安裝EntityFramework.SqlServer.dll,因為安依賴EntityFramework.dll,會自動安裝生成EntityFramework.dll的本地鏡像。
三、禁用第一次ef查詢對表__MigrationHistory的問題
使用了ef的Code first會在第一次ef查詢的時候會對__MigrationHistory通路,是為了檢查資料庫和model是否比對,以保證ef能正常運作。通過監測會先執行下面的sql:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM[dbo].[__MigrationHistory] AS [Extent1]
) AS[GroupBy1]
GO
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[ModelHash] AS [ModelHash]
FROM [dbo].[EdmMetadata] AS [Extent1]
ORDER BY [Extent1].[Id] DESC
GO
這段sql語句其實中隻是在開發的時候有用,釋出到生産環境,可以把這個給禁用了以提高性能。解決辦法:
Application_Start加代碼
Database.SetInitializer<lanhuBlog.DAL.BlogContext>(null);
lanhuBlog.DAL.BlogContext這是我項目的EF上下方類,你要根據你的項目替換成自己的EF上下方類。
四、Model和DAl單獨的分層的
用vs建一個mvc項目,Model、DAL、Controller、View都在Web項目裡面。為了減少model和DAL導緻重新編譯dll帶來的性能影響。我把Model和DAL都單獨的分層,編譯成單獨的dll了。
五、EF Pre-Generated Mapping Views(預生成映射視圖)
Application_Start加入下面代碼:
using (var dbcontext = new EFDbContext())
{
var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext;
var mappingCollection =(StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
mappingCollection.GenerateViews(newList<EdmSchemaError>());
//對程式中定義的所有DbContext逐一進行這個操作
}
六、補充
如果你覺得這還沒有解決”過了一段時間不通路頁面然後再次打開頁面變慢“的問題,而且不能忍受第一次通路還是有點慢,可以設定應用程式池的”閑時逾時“和回收”固定時間間隔“長一些或者建一個計劃任務定時去通路使用了ef的頁面,這樣給ef熱身,讓ef不變冷,這樣可以防止長時間不請求網站,應用程式程序停止再次通路變慢的問題。設定應用程式池的時間如下圖:
閑時逾時預設是20分鐘,如果在超過20分鐘都沒有請求這個應用程式池工作程序就要關閉。這裡你可以設定根據自己需要設定長一些。
本站文章除注明轉載外,均為本站原創或翻譯,歡迎任何形式的轉載,但請務必注明出處,尊重他人勞動,共創和諧網絡環境。
轉載請注明:文章轉載自:藍狐軟體工作室 » 親授MVC5中EF6 Code First啟動慢及間隙變慢優化的實踐經驗
本文标題:親授MVC5中EF6 Code First啟動慢及間隙變慢優化的實踐經驗
本文位址:http://www.lanhusoft.com/Article/127.html