不修改代码就能优化ASP.NET网站性能的一些方法

优采云 发布时间: 2022-05-20 21:39

  不修改代码就能优化ASP.NET网站性能的一些方法

  public class SetOutputCacheModule : IHttpModule

  {

  static SetOutputCacheModule()

  {

  // 加载配置文件

  string xmlFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, "OutputCache.config");

  ConfigManager.LoadConfig(xmlFilePath);

  }

  public void Init(HttpApplication app)

  {

  app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);

  }

  void app_PreRequestHandlerExecute(object sender, EventArgs e)

  {

  HttpApplication app = (HttpApplication)sender;

  Dictionary settings = ConfigManager.Settings;

  if( settings == null )

  throw new ConfigurationErrorsException("SetOutputCacheModule加载配置文件失败。");

  // 实现方法:

  // 查找配置参数,如果找到匹配的请求,就设置OutputCache

  OutputCacheSetting setting = null;

  if( settings.TryGetValue(app.Request.FilePath, out setting) ) {

  setting.SetResponseCache(app.Context);

  }

  }

  ConfigManager类用于读取配置文件,并启用了文件依赖技术,当配置文件更新后,程序会自动重新加载:

  internal static class ConfigManager

  {

  private static readonly string CacheKey = Guid.NewGuid().ToString();

  private static Exception s_loadConfigException;

  private static Dictionary s_settings;

  public static Dictionary Settings

  {

  get{

  Exception exceptin = s_loadConfigException;

  if( exceptin != null )

  throw exceptin;

  return s_settings;

  }

  }

  public static void LoadConfig(string xmlFilePath)

  {

  Dictionary dict = null;

  try {

  OutputCacheConfig config = XmlHelper.XmlDeserializeFromFile(xmlFilePath, Encoding.UTF8);

  dict = config.Settings.ToDictionary(x => x.FilePath, StringComparer.OrdinalIgnoreCase);

  }

  catch( Exception ex ) {

  s_loadConfigException = new System.Configuration.ConfigurationException(

  "初始化SetOutputCacheModule时发生异常,请检查" + xmlFilePath + "文件是否配置正确。", ex);

  }

  if( dict != null ) {

  // 注册缓存移除通知,以便在用户修改了配置文件后自动重新加载。

  // 参考:细说 ASP.NET Cache 及其高级用法

  //

  CacheDependency dep = new CacheDependency(xmlFilePath);

  HttpRuntime.Cache.Insert(CacheKey, xmlFilePath, dep,

  Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, CacheRemovedCallback);

  }

  s_settings = dict;

  }

  private static void CacheRemovedCallback(string key, object value, CacheItemRemovedReason reason)

  {

  string xmlFilePath = (string)value;

  // 由于事件发生时,文件可能还没有完全关闭,所以只好让程序稍等。

  System.Threading.Thread.Sleep(3000);

  // 重新加载配置文件

  LoadConfig(xmlFilePath);

  }

  }

  有了AutoSetOutputCacheModule,我们就可以直接使用配置文件为页面设置OutputCache参数,而不需要修改任何页面,是不是很容易使用?

  说明:MyMVC框架已支持这种功能,所有相关的可以从MyMVC框架的源码中获取。()

  建议:对于一些很少改变的页面,缓存页是一种很有效的优化方法。

  启用内容过期

  每个网站都会有一些资源文件(图片,JS,CSS),这些文件相对于ASPX页面来说, 它们的输出内容极有可能在一段长时间之内不会有变化, 而IIS在响应这类资源文件时不会生成Cache-Control响应头。

  在这种情况下,浏览器或许会缓存它们,也许会再次发起请求(比如重启后),总之就是缓存行为不受控制且缓存时间不够长久。

  有没有想过可以把它们在浏览器中长久缓存起来呢?

  为了告诉浏览器将这些文件长久缓存起来,减少一些无意义的请求(提高页面呈现速度),我们可以在IIS中启用内容过期, 这样设置后,IIS就能生成Cache-Control响应头,明确告诉浏览器将文件缓存多久。

  在IIS6中,这个参数很好找到:

  

  然而,在IIS7中,这个参数不容易被发现,需要以下操作才能找到:

  选择网站(或者网站子目录)节点,双击【HTTP响应标头】

  

  再点击右边的【设置常用标头】链接,

  

  此时将会显示:

  

  说明:【启用内容过期】这个设置可以基于整个网站,也可以针对子目录,或者一个具体的文件。

  注意:如果您在IIS7中针对某个子目录或者文件设置【启用内容过期】,前面的对话框看起来是一模一样的

  然而,在IIS6中,我们可以清楚地从对话框的标题栏中知道我们在做什么:

  

  有时真感觉IIS7的界面在退步!

  最后我想说一句:可以直接为整个网站启用内容过期,ASPX页面是不会被缓存的!

  说到这里可能有人会想:这个过期时间我该设置多久呢?

  十分钟,2个小时,一天,还是一个月?

  在我看来,这个时间越久越好。

  可能有人又会说了:万一我要升级某个JS文件怎么办,时间设置久了,用户怎么更新呢?

  如果你问我这个问题,我也只能说是你的代码不合理(毕竟你解决不了升级问题),想知道原因的话,请继续阅读。

  解决资源文件升级问题

  对于一些规模不大的网站来说,通常会将资源文件与程序文件一起部署到一个网站中。

  这时可能会采用下面的方式来引用JS或者CSS文件:

  在这种情况下,如果使用了前面所说的【启用内容过期】方法,那么当有JS,CSS文件需要升级时, 由于浏览器的缓存还没有过期,所以就不会请求服务器,此时会使用已缓存的版本, 因此可能会出现各种奇怪的BUG

  对于前面谈到的BUG,我认为根源在于引用JS,CSS文件的方式有缺陷, 那种方法完全没有考虑到版本升级问题, 正确的方法有二种:

  1. 给文件名添加版本号,像jquery那样,每个版本一个文件(jquery-1.4.4.min.js)。

  2. 在URL后面添加一个版本号,让原先的URL失效。

  第一种方法由于每次升级都产生了一个新文件,所以不存在缓存问题,但是,维护一大堆文件的成本可能会比较大, 因此我建议采用第二种方法来解决。

  在MyMVC的示例代码中,我使用了下面的方法来引用这些资源文件:

  在页面运行时,会产生如下的输出结果:

  这二个工具方法的实现代码如下(在MyMVC的示例代码中):

  private static readonly string s_root = HttpRuntime.AppDomainAppPath.TrimEnd('\\');

  public static string RefJsFileHtml(string path)

  {

  string filePath = s_root + path.Replace("/", "\\");

  string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();

  return string.Format("\r\n", path, version);

  }

  public static string RefCssFileHtml(string path)

  {

  string filePath = s_root + path.Replace("/", "\\");

  string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();

  return string.Format("\r\n", path, version);

  }

  上面这种获取文件版本号的方法,是一种比较简单的解决方案。

  每个引用的地方在生成HTML代码时,都会访问文件的最后修改时间,这会给磁盘带来一点读的开销, 如果您担心这种实现方式可能会给性能带来影响,那么也可以增加一个配置文件的方式来解决(请自行实现), 例如以下结构:

  xmlns:xsd="">

  如果您认为这种配置文件需要手工维护,不够自动化,还可以采用程序的方式自动在运行时维护一个列表。

  总之,直接引用资源文件的方法是一种直接耦合,会给文件升级带来麻烦, 我们可以通过一个外部方法来解开这个直接耦合(给FileVersion增加一个属性还还可以将内部地址改成一个CDN地址)。

  启用压缩

  压缩响应结果也是常用的网站优化方法,由于现在的浏览器都已支持压缩功能,因此,如果在服务端能压缩响应结果,对于网速较慢的用户来说,会减少很多网络传输时间,最终的体验就是网页显示速度变快了!

  IIS6虽然提供压缩的设置界面,然而配置是基于服务器级别的:

  

  注意:这里的【应用程序文件】不包括aspx,如果需要压缩aspx的响应, 需要手工修改x:\WINDOWS\system32\inetsrv\MetaBase.xml文件(参考加大字号部分):

  HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"

  HcCreateFlags="1"

  HcDoDynamicCompression="TRUE"

  HcDoOnDemandCompression="TRUE"

  HcDoStaticCompression="TRUE"

  HcDynamicCompressionLevel="9"

  HcFileExtensions="htm

  html

  txt

  js

  css

  htc"

  HcOnDemandCompLevel="10"

  HcPriority="1"

  HcScriptFileExtensions="asp

  exe

  aspx

  axd"

  >

  说明:要修改MetaBase.xml,需要停止IIS Admin Service服务。

  在IIS7中,我们可以在服务器级别配置压缩参数:

  

  然后在每个网站中开启或者关闭压缩功能:

  

  说明:

  IIS7中已经不再使用MetaBase.xml,所以我们找不到IIS6的那些设置了。

  IIS7压缩的过滤条件不再针对扩展名,而是采用了mimeType规则(保存在applicationHost.config)。

  根据IIS7的压缩规则,当我们启用动态压缩后,会压缩aspx的响应结果。

  二种压缩方法的差别:

  1. 静态内容压缩:当服务器在第一次响应某个静态文件时,会生成一个压缩后的结果,并保存到磁盘中,以便重用。

  2. 动态内容压缩:【每次】在响应客户端之前,压缩响应结果,在内存中完成,因此会给CPU带来一些负担。

  注意:要不要【启用动态内容压缩】这个参数,需要评估服务器的CPU是否能以承受(观察任务管理器或者查看性能计数器)。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线