随笔- 60  文章- 14  评论- 17 

最近项目在使用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

 posted on 2016-06-15 16:22  heoo  阅读(...)  评论(...编辑  收藏