分享Web应用运行的细节问题:预编译提高网站性能、跟踪用户习惯和解决线程同步

简介:

在这个文章里,我将分享一下在iOpenWorks.com这个网站试运行中碰到的若干问题和解决方案,这些问题包含了:(1)如何通过ASP.NET MVC预编译提高性能;(2)如何知道网站在运行中,用户响应速度、网站异常信息、用户操作习惯;(3)解决与DiscuzToolkit集成的线程同步问题。

 

1 ASP.NET MVC 3预编译支持

提高网站性能,除了我们常见的压缩、CDN、缓存之外,还有一个就是使用预编译。不管是ASP.NET WebForm,或者是ASP.NET MVC,这些页面在网站运行过程中,都是要先经过编译处理的。因此,如果能在网站运行前对其进行编译,那无疑能更好的提高网站的响应速度。因此,我们选择了一个RazorGenerator来对所有的ASP.NET MVC 3的视图进行编译,这样,在部署时仅需要将dll文件拷贝过去,而不再需要cshtml文件了。下面介绍如何使用它来实现预编译。

1.1 下载安装RazorGenerator

你可以在http://razorgenerator.codeplex.com/下载到RazorGenerator,这是一个VS 2010的扩展。下载完成后,就可以直接安装了。接着你还需要下载源代码,然后编译一下,获取编译的RazorGenerator.Mvc.dll程序集。

 

1.2 改变视图文件的生成方式

将所有的视图的BuildAction改成None,并且将CustomTool改成RazorGenerator,这时候,你可以看到一个关联的.generated.cs文件,这个文件就是预编译的源码文件了。

image

1.3 处理Helper

对于Helpr文件,处理方式有所不同。Helper文件一般放在App_Code文件夹里面。首先,你需要在Helper文件的第一行添加 @* Generator: MvcHelper *@ 来声明一下,接着将BuildAction改成None,并且将CustomTool改成RazorGenerator

image

下面,还需要额外一个步骤,这个非常重要,否则编译无法通过,那就是需要将.generated.cs文件的BuildAction由Content改为Compile。

image

 

1.4 注册PrecompiledMvcEngine

下面我们在ASP.NET MVC 3项目中引用RazorGenerator.Mvc.dll这个程序集,然后定义一个PreApplicationStartCode,并在AssemblyInfo.cs文件中注册这个PreApplicationStartCode。这样,我们就注册了PrecompiledMvcEngine了。

(1)在AssemblyInfo.cs注册

 

[assembly: PreApplicationStartMethod( 
     typeof(UIShell.iOpenWorks.PreApplicationStartCode),  " PreStart ")]

 

(2)PreApplicationStartCode定义

 

复制代码
namespace UIShell.iOpenWorks 

     public  class PreApplicationStartCode 
    { 
         private  static  bool _isStarting;
         public  static  void PreStart() 
        { 
             if (!_isStarting) 
            { 
                _isStarting =  true;
                 var engine =  new PrecompiledMvcEngine( 
                     typeof(PreApplicationStartCode).Assembly);
                ViewEngines.Engines.Add(engine);
                VirtualPathFactoryManager.RegisterVirtualPathFactory(engine); 
            } 
        } 
    } 
}
复制代码

 

 

1.5 部署

这时候,部署网站就不再需要将视图文件部署过去了,只需要拷贝dll文件和网站资源。注意,在Views下面已经没有.cshtml文件了,也没有App_Code文件,因为它们都被预编译到了UIShell.iOpenWorks.dll这个程序集了。接下来,你就可以测试一下网站,享受一下预编译带来的性能提升了。

imageimage

 

2 跟踪网站运行情况

网站在内测期间,会碰到较多的问题。但是,这时候,用户已经进来测试,你怎么能够及时发现用户响应速度、用户访问过程中网站异常信息以及用户是如何来使用你的网站。这里,我们使用了log4net这个日志组件,它用于记录:(1)用户访问了哪些页面;(2)用户在访问页面过程中,碰到了哪些异常;(3)每一个页面的响应速度。下面,我来介绍如何记录这些信息的。

2.1 在Global中,跟踪每个用户访问的页面,并且要记录用户响应的速度

 

复制代码
[ThreadStatic] 
private  static Stopwatch _stopwatch;
protected  void Application_BeginRequest() 

    _stopwatch = Stopwatch.StartNew();   //  计时开始 
     if (DiscuzHelper.IsLoggedIn())  //  记录当前用户 
    { 
         try 
        { 
             var user = DiscuzHelper.LoggedUser(); 
             if(user !=  null
            { 
                ThreadContext.Properties[ " user "] = user.UserName; 
                 return
            } 
        } 
         catch (Exception ex) 
        { 
            _logger.Error( " Failed to get the user name though the user is logged in. ", ex); 
        } 
    } 
            
    ThreadContext.Properties[ " user "] =  string.Empty;
     if (Request !=  null//  记录当前用户的IP 
    { 
        ThreadContext.Properties[ " ipaddress "] = Request.ServerVariables[ " REMOTE_ADDR "]; 
    } 
     else 
    { 
        ThreadContext.Properties[ " ipaddress "] =  string.Empty; 
    } 
}
protected  void Application_EndRequest() 

     if (Request !=  null && _stopwatch !=  null && _logger !=  null)   //  计时结束,就用户响应时间和访问页面 
    { 
        _stopwatch.Stop(); 
        _logger.Debug( string.Format( " Accessed page 'Response time: {0} ms, Url: {1}'. ", _stopwatch.ElapsedMilliseconds, Request.Url)); 
    } 
}
复制代码

 

2.2 在Global中,记录系统的异常

 

复制代码
void Application_Error(Object sender, EventArgs ea) 

     if (Server !=  null
    { 
        Exception e; 
         for (e = Server.GetLastError(); e !=  null; e = e.InnerException) 
        { 
            _logger.Error( " Unhandled server exception thrown. ", e); 
        } 
    } 
}
复制代码

 

 

2.3 处理关键方法

下面,我还在关键方法记录了用户的操作异常信息、响应速度。比如我必须记录了:(1)用户注册时响应速度、注册时发生的异常、用户登录时响应速度、用户登录时发生的异常;(2)用户在什么情况下尝试下载iOpenWorksSDK这个免费插件框架;(3)尝试下载时,会转到注册页面,这时候用户是否继续注册并下载,还是放弃。

对这些关键方法的记录,有助于提高应用系统的易用性。通过日志,我们修复了与Discuz集成的很多问题,并且提高了用户响应速度。

 

2.4 日志分析

下面,我们需要来看一下日志分析,这里我们在一个开源的LogViewer自定义了一下。通过对日志的分析,你就可以知道系统发生了什么异常、系统性能如何、用户操作习惯、关键方法的信息。当然,你也可以打开日志文件直接查看,只是,那样比较费劲。对了,在这里我们绝不记录用户的密码,这太不职业道德了,此外,所有密码都是加密的,避免“CSDN”!

(1)查看异常信息

image

(2)查看关键方法信息:用户访问习惯、响应性能等

image

 

3 解决DiscuzToolkit线程同步

网站的社区是与Discuz集成的,我们就用了DiscuzToolkit来集成。这是官方发布的类库,但是依然问题一堆。最严重的2个问题就是线程同步引起的,可见Discuz这帮人对ASP.NET多线程模型压根没有当一回事,或者连线程安全都没有注意到。下面就说一下碰到的2个线程安全问题。

(1)在注册用户时,碰到以下异常:当前会话所提交的call_id没有大于前一次的call_id

Failed to get the user name though the user is logged in.
Discuz.Toolkit.DiscuzException: Code: 103, Message: 当前会话所提交的call_id没有大于前一次的call_id
at Discuz.Toolkit.Util.GetResponse[T](String method_name, DiscuzParam[] parameters) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 97
at Discuz.Toolkit.DiscuzSession.GetUserInfo(Int64[] uids, String[] fields) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 224
at Discuz.Toolkit.DiscuzSession.GetUserInfo(Int64 uid) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 255

这个问题是由Discuz.Toolkit.Util的Sign方法引起的,在这里,它为每一个API请求生成一个call_id。

 

list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks));

 

 

如果你在当前线程API调用太勤快的话,DateTime.Now.Ticks会生成一样的值,从而引发异常。因此,官方提议可以Sleep一下。因此,我们就需要改成如下:

 

list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks)); 
//  Avoid to generate same 'call_id' and throws an exception on '当前会话所提交的call_id没有大于前一次的call_id'. 
Thread.Sleep( 50);

 

 

但是这样,依然是不行的,这个异常只是变得更加诡异了,让你碰到机会少一点而已。你别忘了ASP.NET应用程序是多线程的,当两个线程同时访问时,依然可能获得同一个call_id,于是,在碰到若干次这个问题后,我用以下方法来修复。

 

lock (_syncRoot) 

    list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks)); 
     //  Avoid to generate same 'call_id' and throws an exception on '当前会话所提交的call_id没有大于前一次的call_id'. 
    Thread.Sleep( 50); 
}

 

 

(2)注册用户时,碰到以下异常:An item with the same key has already been added.

[2012-04-07 17:11:30,818] [7] [ERROR] [AccountController] [49.72.46.135] []: System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at Discuz.Toolkit.Util.GetSerializer(Type t) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 157
at Discuz.Toolkit.Util.GetResponse[T](String method_name, DiscuzParam[] parameters) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 88
at Discuz.Toolkit.DiscuzSession.GetUserID(String username) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 243
at UIShell.iOpenWorks.Controllers.AccountController.Register(DiscuzNewUser newUser, String returnUrl) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\UIShell.iOpenWorks\Controllers\AccountController.cs:line 53

 

你想想,要是用户注册时,动不动碰到注册不成功,是多么窝火!!所以,我根据日志再次调查发现,DiscuzToolkit在使用静态变量保存数据时,竟然不加锁,太不拿Thread-Safe当回事了。这会异常也发生在Util类里,代码如下,其中serializer_dict是静态全局变量。

 

serializer_dict.Add(type_hash,  new XmlSerializer(t));

 

于是,我修改如下。这样,彻底解决了和Discuz的集成了。

 

复制代码
private  static Dictionary< int, XmlSerializer> serializer_dict =  new Dictionary< int, XmlSerializer>(); 
private  static ReaderWriterLock _lock =  new ReaderWriterLock(); 
public  static XmlSerializer GetSerializer(Type t) 

     int type_hash = t.GetHashCode(); 
     const  int timeout =  5000;
     try 
    { 
        _lock.AcquireReaderLock(timeout); 
         if (!serializer_dict.ContainsKey(type_hash)) 
        { 
            _lock.UpgradeToWriterLock(timeout); 
             if (!serializer_dict.ContainsKey(type_hash)) 
            { 
                serializer_dict.Add(type_hash,  new XmlSerializer(t)); 
            } 
        }
         return serializer_dict[type_hash]; 
    } 
     catch (ApplicationException ex) 
    { 
         throw  new Exception( " Accquire lock failed. ", ex); 
    } 
     finally 
    { 
         if (_lock.IsReaderLockHeld) 
        { 
            _lock.ReleaseReaderLock(); 
        } 
         else  if (_lock.IsWriterLockHeld) 
        { 
            _lock.ReleaseWriterLock(); 
        } 
    } 
}
复制代码

 

 

OK,关于网站试运行中,最重要的几点分享描述完了。顺道介绍一下什么是iOpenWorks.com。iOpenWorks.com是一个免费开放的插件仓库,旨在向开发人员提供完全免费的标准化的OSGi.NET面向服务插件框架以及共享的插件仓库,这样,你既可以从插件仓库使用别人插件,也可以共享自己的插件,互利共赢!

 

你也可以加入iOpenWorks交流群:121369588,Thanks。


本文转自道法自然博客园博客,原文链接:http://www.cnblogs.com/baihmpgy/archive/2012/04/09/2438720.html,如需转载请自行联系原作者

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
4天前
|
XML 存储 Java
11:Servlet中初始化参数的获取与应用-Java Web
11:Servlet中初始化参数的获取与应用-Java Web
16 3
|
9天前
|
JavaScript 前端开发 持续交付
【专栏】构建现代Web应用:Vue.js与Node.js的完美结合
【4月更文挑战第27天】本文探讨了Vue.js和Node.js如何结合构建现代Web应用。Vue.js作为轻量级前端框架,以其简洁易懂、组件化开发、双向数据绑定和虚拟DOM等特点受到青睐;而Node.js是高性能后端平台,具备事件驱动、非阻塞I/O、丰富生态系统和跨平台优势。两者结合实现前后端分离,高效通信,并支持热更新、持续集成、跨平台和多端适配,为开发高性能、易维护的Web应用提供强有力的支持。
|
9天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
4天前
|
设计模式 存储 前端开发
18:JavaBean简介及其在表单处理与DAO设计模式中的应用-Java Web
18:JavaBean简介及其在表单处理与DAO设计模式中的应用-Java Web
19 4
|
4天前
|
存储 前端开发 安全
13:会话跟踪技术Session的深度应用与实践-Java Web
13:会话跟踪技术Session的深度应用与实践-Java Web
16 3
|
4天前
|
存储 前端开发 搜索推荐
12:会话跟踪技术Cookie的深度应用与实践-Java Web
12:会话跟踪技术Cookie的深度应用与实践-Java Web
16 4
|
5天前
|
缓存 移动开发 前端开发
【专栏:HTML与CSS前端技术趋势篇】HTML与CSS在PWA(Progressive Web Apps)中的应用
【4月更文挑战第30天】PWA(Progressive Web Apps)结合现代Web技术,提供接近原生应用的体验。HTML在PWA中构建页面结构和内容,响应式设计、语义化标签、Manifest文件和离线页面的创建都离不开HTML。CSS则用于定制主题样式、实现动画效果、响应式布局和管理字体图标。两者协同工作,保证PWA在不同设备和网络环境下的快速、可靠和一致性体验。随着前端技术进步,HTML与CSS在PWA中的应用将更广泛。
|
5天前
|
前端开发 JavaScript 搜索推荐
【专栏:HTML 与 CSS 前端技术趋势篇】HTML 与 CSS 在 Web 组件化中的应用
【4月更文挑战第30天】本文探讨了HTML和CSS在Web组件化中的应用及其在前端趋势中的重要性。组件化提高了代码复用、维护性和扩展性。HTML提供组件结构,语义化标签增进可读性,支持用户交互;CSS实现样式封装、布局控制和主题定制。案例展示了导航栏、卡片和模态框组件的创建。响应式设计、动态样式、CSS预处理器和Web组件标准等趋势影响HTML/CSS在组件化中的应用。面对兼容性、代码复杂度和性能优化挑战,需采取相应策略。未来,持续发掘HTML和CSS潜力,推动组件化开发创新,提升Web应用体验。
|
5天前
|
缓存 前端开发 JavaScript
探索现代Web应用的性能优化策略移动应用开发的未来之路:跨平台与原生之争
【4月更文挑战第30天】随着互联网技术的迅猛发展,Web应用已成为信息交流和商业活动的重要平台。用户对Web应用的响应速度和稳定性有着极高的期望,这促使开发者不断寻求提升应用性能的有效途径。本文将深入探讨针对现代Web应用进行性能优化的关键策略,包括前端优化、后端优化以及数据库层面的调优技巧,旨在为开发者提供一套全面的优化工具箱,帮助他们构建更快速、更高效的Web应用。
|
6天前
|
开发框架 JavaScript 前端开发
【JavaScript 与 TypeScript 技术专栏】TypeScript 在 Web 开发中的前沿应用
【4月更文挑战第30天】TypeScript在Web开发中日益重要,以其强大的类型系统提升代码质量,支持组件化开发,与React、Vue、Angular等框架良好集成。在大型项目管理中,TypeScript助于代码组织和优化,提高团队协作效率。此外,它提升开发体验,提供智能提示和错误检测。众多成功案例证明其前沿应用,未来将在Web开发领域持续发挥关键作用。