最近在重构一个以前团队开发留下的MVC项目,项目结构堪称混乱,问题多多,但今天说的是页面打开速度的问题。项目中包括web后台系统,几乎随便点一个页面都要盯着白屏等待2-5秒之久,体验很差。通过对页面性能逐步的分析和判断,并做相应优化,最终页面打开速度在1秒内,发布后打开速度约60ms。

一、项目结构

后台系统开发使用了典型的微软ASP.Net MVC框架,但是版本略旧,.Net Framework4,MVC4,EF5,基本没有采用其它的技术工具,业务逻辑大致都混乱的分布在Controller和Model层。习惯了ABP框架,相比之下这个就显得简陋了。至于View层,摘要中提到了白屏,可以知道前端页面使用多页开发,后台服务器渲染,正是Razor模板引擎,版本2.0。

登录后台系统,随便点击左侧菜单,右侧打开页面,大概需要白屏等待2-5秒。咦,怎么会如此之慢?!

于是基于一个典型的页面,开始进行监测分析。我选择了[所有用户]菜单,页面打开速度在3秒左右,页面结构和功能都相对简单,上面是一个搜索条件区域,可以按照姓名部门角色等条件组合搜索,下面就是用户列表和分页,展示大约10个字段,大致示意图如下(虽然是测试阶段,但数据真实,为避免泄露客户信息,而且页面结构简单易于描述,所以不做截屏展示)。

二、问题分析

系统架构简单,页面的大概执行周期如下图示:

从中基本推断出耗时操作可能发生在

  1. 网络传输
  2. 数据库操作
  3. 业务逻辑执行
  4. Razor渲染
  5. 浏览器页面渲染

用排除法,我首先检查了客户端浏览器,Web服务器,数据库服务器的网络环境都正常,页面使用Bundle合并压缩了JS,CSS,数据库操作都是简单的查询,所以主要跟传输耗时相关的1,2两项可以先排除。

下一步,我检查了业务执行逻辑,也是很简单的根据搜索条件查询数据,并返回View,不存在很复杂的逻辑调用,3项排除。

然后,页面渲染也没有特殊之处,虽然也没有特别优化,但这种简单的页面渲染耗时应该大概在50ms,5项也可以排除了。

引用福尔摩斯的一句话“排除所有的不可能,剩下的即使再不可能,那也是真相。”,我已久不用Razor开发多页应用,虽不是很了解,但以前记得Razor的效率也没有这么低啊,一个渲染模板引擎的执行至于耗费2秒吗?

好吧,无论如何目前这项是最可疑的,先用Log4Net查看下视图渲染用了多少时间,证明下我的猜想。

ActionFilterAttribute中监测记录View用时经典代码:

         //View生成时间监控
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
MonitorLog MonLog = filterContext.Controller.ViewData[Key] as MonitorLog;
MonLog.ExecuteStartTime = DateTime.Now;
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
MonitorLog MonLog = filterContext.Controller.ViewData[Key] as MonitorLog;
MonLog.ExecuteEndTime = DateTime.Now;
LoggerHelper.Monitor(MonLog.GetLoginfo(MonitorLog.MonitorType.View));
filterContext.Controller.ViewData.Remove(Key);
}

结果似乎印证了我的猜想,如下图。

渲染出视图居然耗费了2.6秒之多!是因为Razor低版本存在的问题吗?有配置不合理吗?Razor本身就这么低效吗?瞬间随之几个想法又冒了出来。

三、改进

随后进一步查询了关于Razor的技术信息,做了下面两个方面的改进。

1.在global.asax文件Application_Start()方法中设置仅使用Razor视图引擎。由于默认MVC会首先加载WebForm视图引擎,而本项目中仅用Razor引擎。

 //只使用Razor引擎
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());

2.设置Web.Config,设置debug模式为false。

 <system.web>
<compilation debug="false" targetFramework="4.0" />
......

这会带来几个方面的优化,如视图文件地址缓存,启用请求超时,Bundle功能生效等。

这些也似乎没有什么好优化的,然后,我再次运行页面,发现打开时间几乎没有减少,WTF?!

四、继续改进

再看监测日志,视图渲染仍然用去大概2秒以上,这其中必有蹊跷,不达目的不罢休,于是我又引入了性能监测工具,MiniProfiler。

然后再次打开页面,看到红色的监测提示时,瞬间明白了问题所在。

这个请求过程中sql请求占用90%以上的时间,居然使用了31条SQL,而且我打开看重复语句达20多条!

按道理说,我在看业务逻辑代码时,如果代码中会产生这么多SQL调用,一眼就可以看出来了,然而并没有,而且日志记录了时间耗费是计算在视图渲染中的。

我恍然大悟,如果自己来写Razor渲染的cshtml代码,一定会将Model数据完善后,传过来直接使用,不可能在cshtml代码中再去请求数据,而这里cshtml中的代码,却在反复的去请求数据库数据。

后台的Linq语句大概是db.Users.ToList(),cshtml页面Model为@model PagedList<Models.Users>,而嵌在cshtml中的除了 model.UserName这一类,还有大量的model.Employee.EmpNo,即有的用户有个内部员工身份,大量的延迟加载导航属性导致反复的数据库访问请求。

那么到这里,问题也就明晰了,在后台的Linq语句中,应当把这些导航属性一次性Include进来,如.Include(x=>x.Employee),还有.Include(x=>x.Roles)等等,修改后再来看打开页面的监测结果。

可以看到用时从2769ms减少到656ms,页面打开用时较之前减少了四分之三,控制在1秒之内,发布之后页面打开用时约60ms,sql仅仅用了5条便完成业务逻辑,这才是asp.net mvc最起码该有的样子。

--End

记一次完整的asp.net-mvc页面优化过程的更多相关文章

  1. 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites

    摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...

  2. Asp.Net MVC 页面代码压缩筛选器-自定义删除无效内容

    Asp.Net MVC 页面代码压缩筛选器 首先定义以下筛选器,用于代码压缩. /*页面压缩 筛选器*/ public class WhiteSpaceFilter : Stream { privat ...

  3. ASP.NET MVC性能优化工具 MiniProfiler

    ASP.NET MVC性能优化工具 MiniProfiler 2014年04月19日 ⁄ ASP.NET ⁄ 共 1159字 ⁄ 字号 小 中 大 ⁄ 暂无评论 ⁄ 阅读 325 views 次 MV ...

  4. Asp.Net MVC页面静态化功能实现二:用递归算法来实现

    上一篇提到采用IHttpModule来实现当用户访问网站的时候,通过重新定义Response.Filter来实现将返回给客户端的html代码保存,以便用户下一次访问是直接访问静态页面. Asp.Net ...

  5. Asp.Net MVC页面静态化功能实现一:利用IHttpModule,摒弃ResultFilter

    上一篇有提到利用IHttpModule和ResultFilter实现页面静态化功能.后来经过一些改动,将ResultFilter中要实现的功能全部转移到IHttpModule中来实现 Asp.Net ...

  6. Asp.Net MVC页面静态化功能实现一:利用IHttpModule和ResultFilter

    由于公司现在所采用的是一套CMS内容管理系统的框架,所以最近项目中有一个需求提到要求实现页面静态化的功能.在网上查询了一些资料和文献,最后采用的是小尾鱼的池塘提供的 利用ResultFilter实现a ...

  7. asp.net mvc 页面内容呈现Html.Raw HtmlString

    asp.net mvc 页面内容呈现Html.Raw Html.Raw内容经过页面呈现,不呈现Html标签 @Html.Raw( File.ReadAllText(Server.MapPath(&qu ...

  8. asp.net mvc 性能优化——(1)静态化

    asp.net mvc 性能优化--(1)静态化 在改善页面性能的同时,可能会采用静态化的策略,对于不能实时静态化的内容,则采用缓存.本文主要讨论如何实现cshtml的静态化(实际上还不是完全的htm ...

  9. 使用Filter跟踪Asp.net MVC页面加载时间

    最近,客户一直反馈系统使用慢,有时候能够指出具体是哪个页面,有时候又只是笼统地反馈慢.这种问题就像是幽灵一样,非常不好处理.因为导致这种问题的因素非常之多,而且在开发工程中,很难模拟出实际运行是的环境 ...

随机推荐

  1. spring AOP的概念和使用

    指路:http://www.cnblogs.com/liujiayun/p/5912628.html AOP的来源:OOP在一些重复代码的使用中,代码冗余比较多,利用AOP可以改善. 通过编写切面和切 ...

  2. python验证卡普耶卡(D.R.Kaprekar)6174猜想

    1955年,卡普耶卡(D.R.Kaprekar)对4位数字进行了研究,发现一个规律: 对任意各位数字不相同的4位数,使用各位数字能组成的最大数减去能组成的最小数,对得到的差重复这个操作,最终会得到61 ...

  3. 初识Kotlin之变量

    用Java开发了很多年,因为工作的需要学习Kotlin.初识Kotlin时是各种不习惯,觉得这个语言相对于Java而言并不够严谨.随着不断的深入,最终还是逃不过"真香定理".我一直 ...

  4. Java(13) 抽象和封装

    一.简述从现实世界中抽象出类的步骤 第一:找出分类(分析出类) 第二:找出类的特征(分析类的相关属性) 第三:找出类的行为(分析类的方法) 二.常量(经常出现的变量值) 2.1 语法: public ...

  5. Java中快捷键

    Fond表示字体 size表示字号 IDEA的基本配置 IDEA中常用的快捷键 Intellij IDEA基本快捷键 Ctrl+G 跳转到指定行 Ctrl+F4 关闭当前编辑页面 Ctrl+F 搜索 ...

  6. 在线制作微信跳转浏览器下载app/打开指定页面源码

    微信自动跳转外部浏览器下载app/打开指定页面源码 源码说明: 适用安卓和苹果系统,支持任何网页链接.并且无论链接是否已经被微信拦截,均可实现微信内自动跳转浏览器打开. 生成的跳转链接具有极佳的防拦截 ...

  7. 红警大战JAVA简单版

    代码结构: 相关源码: 武器类: 属性:武器,攻击力,子弹数量. 方法:给属性赋值(set属性()方法) 获取属性值(get属性()方法) package 红警大战简单版; public class ...

  8. TensorFlow GPU版本号与CUDA的对应产生的错误

    前言 感悟:cuda 8.0+cudnn 6.0+TensorFlow 1.3  cuda 9.0+cudnn 7.0+TensorFlow 1.7 python3.6.2+cuda 9.0+cudn ...

  9. pythonのdjango 在控制台用log打印操作日志

    在Django项目的settings.py文件中,在最后复制粘贴如下代码: LOGGING = { 'version': 1, 'disable_existing_loggers': False, ' ...

  10. C++变量/函数命名规范

    ## 参照Google C++编程规范之变量命名 1. 变量 变量名一律小写,单词间以下划线相连.类的成员变量以下划线结尾. 普通变量命名 举例: string window_name; // OK ...