Entity Framework 6 预热、启动优化

虽然文章题目是针对EF的,但涉及的内容不只仅是EF。html

场景介绍

目前在作的一个项目,行业门户,项目部分站点按域名划分以下:程序员

  1. user.xxx.com:用户登录注册
  2. owner.xxx.com:我的用户后台
  3. company.xxx.com:企业后台
  4. manage.xxx.com:网站管理

其中user.xxx.com为我的用户及企业用户登录入口,
manage.xxx.com/login为网站管理后台登录入口。数据库

四个项目都是mvc4+ef6+autofac+automapper。swift

补充信息:服务器

  • .net framework 4.0
  • asp.net mvc4
  • Entity Framework 6
  • 服务器:阿里云ECS win2008 r2 双核 4G内存 IIS7.5

因而,用过ef的都知道,ef首次访问数据库的时候,耗费的时间很长。
若是四个项目都是首次访问,那么我的用户首次登录的时候,会经历两次ef首次访问(user站和owner站)。架构

暂不论项目自己是否有更好的架构方案,或者配合集群加一层进行可用性缓冲等。
本文的目标就是要尽量的下降用户在上述状况下遇到的等待时间。mvc

如下全部测试前提:app

  1. 站点代码有添加优化手段的状况下,回收程序池,并重启站点;
  2. 站点代码未变化的状况下,回收程序池,并重启站点。

初始状态

贴图不太方便,只给搜集的数据做为展现
无任何优化措施的初始状态,更新各站点后:asp.net

  1. 首次打开user:5.47s (注意,此时未使用到EF,仅一个空的登录表单);
  2. 登录owner后台:user处理登录过程(9.37s,涉及用EF访问数据库),owner响应(14.54s,首次访问站点耗时+EF访问数据库耗时,仅涉及用户验证,后台首页为空);
  3. 注销owner后台,回到user,登录company后台,company后台响应(15.19s,首次访问站点耗时+EF访问数据库耗时,仅涉及企业验证,后台首页为空);
  4. 注销company后台,打开manage,manage响应并跳转到登录页(2.96s,首次访问);
  5. 登录manage,耗时(8.23s,处理登录过程涉及EF)

简单概括下就是:less

1. owner登录体验到的延迟共计(5.47s + 9.37s + 14.54s) 2. company登录体验到的延迟共计(15.19s,要感谢登录owner的时候已经"激活"了user以及user的EF) 3. manage登录体验到的延迟共计(2.96s + 8.23s)

第一个问题,站点首次访问的耗时大约5秒

这时候还没EF什么事,只是一个空的登录表单

首次访问,通常分两块:

  1. 站点更新后从新加载程序文件;
  2. iis程序池回收后也会须要从新加载(程序池默认是按需触发运行的,没人访问它就不启动了)

不少.net程序员会忽略这个问题。
(这真的是许多年的无奈经验之谈,大多会说,第一次访问原本就会很慢。)
或者经过脚本定时访问,以规避这个问题(不让用户遇到就好了)

这里我倒想真的试试解决这个问题。

第一个问题的解决方案: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://files.dotblogs.com.tw/jaigi/1306/2013619347830.zip

具体配置方法见:

让IIS 7 如同IIS 8 第一次请求不变慢

若是仅配置程序池StartMode为AlwaysRunning还不放心的话,
也能够同时针对站点开启preload和DoAppInitAfterRestart。

配置好后,测试了下,效果十分不错。
回收程序池后首次打开各站点,延迟都很低。
其实这个模块的思路和定时从外部触发一个访问是同样的,只是,更好的地方在于,它自己在程序池回收重启的时候就完成了这件事,而不会让外部访问有机会遇到首次访问的状况。

好了,完成这一步,解开了多年心结,省了5秒!

第二个优化点:EF Pre-Generated Mapping Views,大概节省4秒

这个优化点,通常仔细去EF的网站找找仍是容易找到的。
具体缘由原理不说了,这里引用下博客园dudu大神的文章。

来,给Entity Framework热热身

搬一下代码:

using (var dbcontext = new CnblogsDbContext()) {     var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext;     var mappingCollection = (StorageMappingItemCollection)objectContext     	.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);     mappingCollection.GenerateViews(new List<EdmSchemaError>()); } //对程序中定义的全部DbContext逐一进行这个操做

我把它配置在每一个站点的Application_Start中了,个人项目使用了Autofac和Repository+UnitOfWork模式,没有异常。

通过这一环节,又砍掉了剩下延迟中的50%时间,大概4秒多点。
概括下测试数据:(结合以上两种优化后的成果)

1. owner登录体验到的延迟共计( <1s + 4.68 + 4.99) 2. company登录体验到的延迟共计(5.5,一样要感谢登录owner的时候已经"激活"了user的EF) 3. manage登录体验到的延迟共计(4.23)

第三个优化点(杀手锏):使用Ngen建立EntityFramework的本地代码镜像(EF版本6以上)

EF的文档要认真看啊!这个真是不当心挖出来的解决方案,主要是被我看到了一句话:

The .NET Framework supports the generation of native images for managed applications and libraries as a way to help applications start faster and also in some cases use less memory.

具体参考:Improving Startup Performance with NGen (EF6 Onwards)

Ngen使用方法:

安装命令:[path to ngen]/ngen.exe install "[path to dll]" 查询命令:[path to ngen]/ngen.exe display System.Xaml /verbose|findstr "EntityFramework" 卸载命令:[path to ngen]/ngen.exe uninstall "[DisplayName in System.Xaml]"

具体原理就不解释了。这里就记一下使用经验:

  1. ngen 安装后,不用试图去寻找生成的.ni.dll文件在哪,这是系统自己起做用的,就至关于已经安装好了
  2. 卸载时候用的标识名是查询命令中查到的包含版本信息和PublicToken的完整dll名
  3. path to dll 不必指向你部署的站点中的bin目录,你能够建个目录(好比系统盘根目录下建个NgenTargets目录),把目标dll拷出来放进去。
  4. ngen安装后,站点中的dll文件不能够删除,仍是保持原样,删掉会500的。这个做用机制是系统级别的,和站点没啥关系,并且一次ngen安装后,全部站点都能享受到这个提速。
  5. EF的provider等相关dll也能够试试,这个我还没试。

这一步以后,概括下测试数据:

1. owner登录体验到的延迟共计(45ms+1.02s+1.06s) 2. company登录体验到的延迟共计(1.42s,一样要感谢登录owner的时候已经"激活"了user的EF) 3. manage登录体验到的延迟共计(925ms+197ms)

哇咔咔,畅快!

至此,心愿已了~
Ngen这种工具,不知道mono有没有,但愿vnext正式版出来后,还能再见。

参考

让IIS 7 如同IIS 8 第一次请求不变慢
Pre-Generated Mapping Views
来,给Entity Framework热热身
Performance Considerations for Entity Framework 4, 5, and 6
Improving Startup Performance with NGen (EF6 Onwards)

相关文章
相关标签/搜索