Asp.NET有许多秘密,当你了解了这些秘密后,可以使得你的ASP.NET应用程序达到极大的性能提升。举例来说,在使用Membership和profile进行Authentication和authorization时,可以通过简单的修改Membership和profile以提高Authentication和authorization的性能;ASP.NET Http 管道通过调整以避免执行不必要的HttpModules;不仅如此,浏览器端的页面缓存可以为重复浏览节省大量的下载时间;按需UI加载可以使网站更快、更平滑;最后,t适当的应用CDN(Cotent Delivery Networks)和应用Http Cache Header可以使网站更加快速。下面就让我们来分别讨论这些技巧,以提高我们ASP.NET网站的性能。
1 ASP.NET 管道最优化配置
用户每次请求网站时,都会有许多ASP.NET默认HttpModules去中断请求并进行一些处理,例如SessisonStateModule会中断每次的请求,解析session cookie信息并加载的适当的Session给HttpContent.但是这些默认的HttpModules并不都是必须的,例如如果你没有应用Forms Authentication来进行用户验证,那就不需要FormsAuthentication Module。
默认的Modules是在machine.config文件(该文件的位置为:$WINDOWS$\Microsoft.NET\Framework\$VERSION$\CONFIG directory))定义的,其定义如下:.
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule,
System.Web.Mobile, Version=1.0.5000.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</httpModules>
你要以通过在web.config中添加<remove>节点来移除不必要的httpModules,比如如果一个网站使用的是基于数据库的forms authentication验证并且没有使用任何Session则可以在web.config中添加如下节点:
<httpModules>
<!-- Remove unnecessary Http Modules for faster pipeline -->
<remove name="Session" />
<remove name="WindowsAuthentication" />
<remove name="PassportAuthentication" />
<remove name="AnonymousIdentification" />
<remove name="UrlAuthorization" />
<remove name="FileAuthorization" />
</httpModules>
2 ASP.NET进程最优化配置
ASP.NET进程模块配置了许多进程级别的属性,比如Asp.NET可以使用多少线程;超时多长时间后阻塞一个线程;在IO线程完成工作时,应该由多少个请求保持等待。默认的配置有许多限制,但是现在硬件变得越来越便宜,双核处理和GB的内存变成了标准配置,所以我们要以调整默认配置,让Asp.net进程使用更多的系统资源。
默认情况下,mechine.config文件中是这样配置asp.net进程的。
<system.web>
<processModel autoConfig="true" />
我们可以调整默认配置,为每个属性指定不同的值,以定制asp.net线程的工作模型,例如:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="00:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="machine"
password="AutoGenerate"
logLevel="Errors"
clientConnectedCheck="00:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
responseRestartDeadlockInterval="00:03:00"
autoConfig="false"
maxWorkerThreads="100"
maxIoThreads="100"
minWorkerThreads="40"
minIoThreads="30"
serverErrorMessageFile=""
pingFrequency="Infinite"
pingTimeout="Infinite"
asyncOption="20"
maxAppDomains="2000"
/>
上面的属性基本上都是使用的默认值,除了下面的几个:
- maxWorkerThreads :maxWorkerThreads的默认值是20/进程。在一个双核处理器的服务器中,会有40个线程分配给asp.net,这意味着双核服务器的asp.net在同一时间只能并行处理40个请求,为了分配给asp.net更多的线程,我把maxWorkerThreads的值改为100。特别需要注意的是,如果asp.net用完了线程,这时如果有新的请求进来,asp.net会将新来的请求放到等待队列,直到有线程释放为止,所以如果你的应用调用了许多web service或是有许多i/o操作,并且这些操作不会占用太多的cpu资源的话,你完全可以增大maxWorkerThreads的值。
- maxIOThreads :maxIOThreads的默认值也是20/process,在一个双核处理器上,同样也有40个线程分配给asp.net来进行I/O操作,这也意味着在一个双核处理器上,同一时间asp.net只能并行处理40个I/O请求。这里我们说的I/O请求可以是文件读写、数据库操作、web service调用等。所以如果你的应用程序服务器有足够的资源来并行处理这些操作,你完全可以将maxIOThreads的值设的大一些,比如100.
- minWorkerThreads :minWorkerThreads的默认值为1,它指出了分配给asp.net的最少辅助线程数。
- minIOThreads : minWorkerThreads的默认值为1,它指出了分配给asp.net的最少I/O辅助线程数。
- memoryLimit:以百分比的形式指定在 ASP.NET 启动新进程和重新分配现有请求前,辅助进程可以使用的最大内存大小。memoryLimit的默认值是60,如果你的服务器上只有你自己的应用程序,并且没有别的消耗内存的服务,那么你完全可以将memoryLimit设的大一些,比如80。
除了processModel之外,还有另外一个重要的节点要以配置,就是你可以在System.Net节点配置对一个IP所允许的最大并发请求数:
<system.net>
<connectionManagement>
<add address="*" maxconnection="100" />
</connectionManagement>
</system.net>
maxconnection的默认值是2,这意味着你不能在你的应用程序里同时对一个IP进行多于2个的请求。所以如果你的应用对一个特定的服务器进行了许多调用,你完全可以将maxconnection的值设的大一点,例如我在这里设为100。
3 在应用程序上线前必须做的事情
如果你的应用程序中应用了Membership Provider,那么在应用程序上线前,你不得不对web.config做一些调整:
- 在Profile Provider中添加applicationname属性,如果你没有指定applicationname的值,那么profile Provider会将其值设为一个GUID,这意味着在你的开发机器上applicationname是一个GUID,在产品服务器上application是另一个GUID,所以你就没有重用开发机器上数据库里关于membership的数据。所以请按下面的格式指定applicationname的值:
<profile enabled="true">
<providers>
<clear />
<add name="..." type="System.Web.Profile.SqlProfileProvider"
connectionStringName="..." applicationName="YourApplicationName"
description="..." />
</providers>
- 每当一次请求完全,profile provider都会自动保存profile,这样我们的应用程序就因为大量的非必须的update操作降低了性能,所以关闭profile的自动保存并且在需要时,显示的通过Profile.Save()来保存。
<profile enabled="true" automaticSaveEnabled="false" >
- Role Manager每次都需要查询数据库来确定一个用户的角色,这同样也是一个很大的性能损失,我们可以通过将用户的角色缓存在cookie中来解决这个问题,但是请注意cookie的最大容量是2KB,所以如果我们有许多用户并且每个用户都有特别多的角色,就有可能超出cookie的容量,当然这种情况毕竟是少数,所以通常我们可以通过下面的设置将用户角色信息缓存在cookie中,以减少数据库访问次数。
<roleManager enabled="true" cacheRolesInCookie="true" >
4 在客户端缓存Ajax请求
浏览器可以缓存图片、js文件、css文件,同样浏览器也可以缓存XML Http调用(当然这需要XML Http以get方式发送调用),这种缓存基于URL,当我们发送一个请求时,如果和以前请求的URL相同,那么浏览器就会从缓存中响应,而不是从重新请求服务器,这可以节省一些时间。通常来说,浏览器会缓存所有的Http get请求的结果,所以如果我们让XML Http以get方式发送请求,并且服务器返回特定的header告诉浏览器缓存结果,那么在以后的请求中,我们就可以直接从缓存中得到响应,从而节约了大量的时间。
在PageFlakes网站中,作者缓存了用户状态,所以如果用户重新登录网站,用户将会从浏览器缓存中得到一个缓存的页面而不是重新请求服务器,所以第二次访问是快很多,另外作者还缓存了许多用户的操作结果,所以当用户重新做相同的动作时,被缓存的结果会迅速的出现,这就使用户得到了更好的体验。
所以如果以http get方式调用Ajax请求时,我们应该返回特定的http header以告诉浏览器缓存请求的结果。下面是两种不同的方式,我们可以采取其中之一:
HTTP/1.1 200 OK
Expires: Fri, 1 Jan 2030
Cache-Control: public
上面的header告诉浏览器将请求结果缓存到2030年,所以如果我们在2030年之前发出同样的xml http请求,浏览器都会直接从缓存中读取结果,而不用重新请求服务器。另外还有一种更高级的header用来控件请求结果的缓存,下面的header指示浏览器也将请求结果缓存60分钟,并在60分钟后重新请求结果,
HTTP/1.1 200 OK
Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60
让我们尝试在Asp.NET web service响应中加入上述的header:
[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet()
{
TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Context.Response.Cache.SetMaxAge(cacheDuration);
Context.Response.Cache.AppendCacheExtension(
"must-revalidate, proxy-revalidate");
return DateTime.Now.ToString();
}
上面的代码将会产生如下的response headwers :
从上图我们可以看到,Expires属性被正确的设置了,但是cache-control属性却没有被正确设置,其max-age的值是0,而不是60,这样浏览器就不会做任何缓存,所以当我们重新请求时,还是会请求服务器,而不是从缓存中返回结果。
这是Asp.net 2.0的一个bug,你无法改变max-age的值,这是因为在处理一个web services请求之前,asp.net ajax framework截取了请求并且错误地将max-age设置为0,这就意味着浏览器不会缓存ajax响应。
但是通过反编译HttpCachePolicy的代码,我们发现了如下代码段:
不知怎么地,只有在"if (!this._isMaxAgeSet || (delta
<
this._maxAge))
" 时,才会设置_maxAge的值,这是不正确的,因为这阻止了将_maxAge的值设为一个大于当前值的值。所以我们需要通过反射来绕过SetMaxAge方法去设置_maxAge的值:
[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet2()
{
TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
FieldInfo maxAge = Context.Response.Cache.GetType().GetField("_maxAge",
BindingFlags.Instance|BindingFlags.NonPublic);
maxAge.SetValue(Context.Response.Cache, cacheDuration);
Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Context.Response.Cache.AppendCacheExtension(
"must-revalidate, proxy-revalidate");
return DateTime.Now.ToString();
}
上面的代码返回如下的header:
现在我们可以看到 max-age的值是60,并且浏览器会缓存结果60分钟,如果你在60分钟内进行相同的调用,浏览器将会从缓存中响应。一分钟后,缓存过期,浏览器会自动发送一个请求,那个请求的代码就像下面一样:
function testCache()
{
TestService.CachedGet(function(result)
{
debug.trace(result);
});
}
请注意,这里还有一个问题,因为asp.net ajax的默认信任级别是中等的,
<system.web>
<trust level="Medium"/>
而我们的CachedGet2方法却用到了反射,所以仅有中等信任是不够的,我们需要按如下的方式修改web.config文件:
<system.web>
<trust level="Full"/>
5 更好的应用浏览器缓存
使用一致的URL
浏览器缓存的基础是URL,当一个URL改变时,浏览器就会从服务器重新请求页面。我们可以通过不同的查询参数来改变URL,例如我们在缓存中缓存了default.aspx页面,如果我们再请求default.aspx/?1页面,这时虽然是相同的操作,但浏览器仍会重新请求服务器,另外如果在Header中做了设置,新的请求结果也会被缓存。因此我们要确保使用一致的URL。一个常见的错误是在是URL中省略www,例如www.cnblogs.com/zhangronghua和cnblogs.com/zhangronghua会被分别缓存。
将静态内容的缓存周期设的长些
静态文件可以设置一个长的缓存周期,比如一个月。如果你认为你应该缓存两天,这样当你改变了文件后,用户就可以很快的得到新的文件,这就你错了,当你更新了一个被expires缓存的文件后,新用户会立即得到更改后的文件,而老用户只有在缓存到期后才能得到更改后的文件。所以只要你应用了Expires去缓存静态文件,就应该将过期周期设的尽可能地大。
例如如果你将一个静态文件的缓存周期设为三天,一个用户A在今天访问页面并将其缓存三天,另一个用户B在明天访问页面并同样缓存页面。所以如果你在后天改变了文件,那么用户A将会在第四天看到新文件,用户B将会在第五天看到新文件,这就会导致不同的用户看到不同的文件,这不是我们想要的结果,所以如果我们更改了文件并将想让用户立即看到更改后的文件,我们应该通过不同的URL来访问文件。
使用缓存友好地目录结构
尽量将需要缓存的文件放在一个目录中,例如将所有的image放在images目录中,而不是分别放在不同的目录中,这样我们就要以在整个网站中使用一致的URL来请求图片。
重用公共图片
有时我们为了少写一些路径,会将一个公共图片放到许多不同的目录中,这样我们就会在文件中只使用图片名来引用图片,而不是基于相同路径,这样做虽说方便了我们的书写,却不是缓存友好的,公共文件的每一个copy都会在缓存中缓存一份。所以我们需要做的就是将公共文件放到一个统一的目录中,在页面中使用相对路径来引用它。
当你修改静态文件后,记得修改文件名
当你修改了一个静态文件后,不要以为修改好了文件就万事大吉了,因为静态文件会在客户端缓存,所以当你修改了静态文件后,一定要记得为其重命名,因为这样可 以确保用户可以立即看到你修改后的文件,而不是缓存了浏览器缓存中的修改前的文件。
使用版本号去访问静态文件
如果你不想每次修改静态文件名后都为其重命名,那么你也可以使用带版本号的URL去访问静态文件,例如GIF文件可以通过带哑查询字符中的URL访问,你可以通过/images/1.gif?v=1去访问1.gif文件,当1.gif文件改变后,你要以通过/images/1.gif?v=2来访问1.gif文件,这样也可以保证用户会立即看到修改后的文件。
将缓存文件放在单独的域中
将缓存文件放在一个单独的域中是一个好主意。首先浏览器也以并发的请求静态文件。其次你不必发送cookies给静态文件,当你将静态文件和应用程序放在同一个域中时,浏览器会在你请求静态文件时,将应用程序产生的cookies也一并发送,而这些cookies对静态文件不是必须的。所以如果我们将静态文件单独放在一个域中,就要以将避免发送这些cookies。例如我们可以将静态文件放在www.zhangronghua.com中,而将我们的应用程序放在真正的www.application.com中,www.zhangronghua.com不必是一个真正的网站,它可以只是一个别名并且和www.application.com指向共同的目录。
SSL 是非缓存的,所以请适当使用
SSL是非缓存的,所以不要通过SSL请求静态文件,另外,我们仅应该在登录页面和支付页面这样的情境下使用SSL,除此之外不应该在程序中使用SSL。SSL需要加密请求和响应,所以增加了服务器的负担,同时加密后的响应内容也更长,会占用更多的带宽。
HTTP POST请求不会被缓存
浏览器只会缓存Http Get请求,不会缓存Http Post请求,所以如果你想要缓存一个文件时,请确保以http get方式请求文件。
显示指明Content-Length 属性
当你通过web service或是http hadler请求一个动态内容时,请确保设置了response header的content-length属性。当浏览器从响应的header中读取到content-lengtg属性时,它会对响应做一些优化以加快下载速度。当指定了content-length后,浏览器会使用持续的连接请求资源,这就避免了浏览器针对每次请求都新建连接。
如何在IIS中缓存静态内容
在IIS管理器中,右击应用程序名,选择“属性”,我们可以看到下图:
选择“启用内容过期”就可以缓存静态内容了。具体的设置很简单,这里就不说了。
6 总结
以上列举了Asp.NET应用程序的部分优化方法,主要包括Asp.net管道最优化配置、Asp.net线程最优化配置和静态文件、Ajax调用缓存。希望对大家所帮助!