ASP.net 2.0:我还有多少秘密你不知道?(2)
地址:http://blog.downke.cn/post/2009/06/08/yhaspnet.aspx
我们要讨论的问题:
》管线最佳优化
》asp.net过程最佳处理
》进行之前要对asp.net做的事情
》内容传送网络
》在浏览器中缓存AJAX请求
》优化缓存机制
》优化页面装载速度提高用户体验
》尽可能的优化ASP.net 2.0的profile provider.
》在不布置网站的情况下如何查询membership表
》DOS攻击
以上技巧可用于任何基于asp.net 2.0的网站,尤其是使用了membership和profile provider的网站。
如果你的网站使用了ASP.net 2.0的Membership Provider,那么你在部署到你的作品服务器之前,要对你的web.config做一些优化。
(1)在profile provider 里添加一个applicationname属性,如果不添,profile 的提供器会使用一个随机的Guid。 这样的话,你本地会使用一个guid而作品服务器上会使用另外一个guid.如果你复制本地的数据库到服务器上,你就不能使用本地数据库里的记录,而且asp.net会在作品服务器上重新创建一个项目。所以你要进行如下的设置:
<profile enabled="true">
<providers>
<clear />
<add name="..." type="System.Web.Profile.SqlProfileProvider"
connectionStringName="..." applicationName="YourApplicationName"
description="..." />
</providers>
(2)当页面请求关闭时profile提供器会自动保存profile的状态,这样会引起一些不必要的数据库操作,对项目性能影响很大,所以应该做些设置:
<profile enabled="true" automaticSaveEnabled="false" >
(3)角色管理经常要查询数据库来得到用户的角色。这样也会造成性能损失。我们完全可以避免这些把角色信息存储到cookie里。但是这样会受制于cookie的2K的大小限制,只适用于用户角色比较少的时候。但是一般情况下不会超出cookie的极限大小。所以我们可以把用户角色存到cookie,这样就会节约请求*.aspx,*.asmx文件时候的数据库操作。
<roleManager enabled="true" cacheRolesInCookie="true" >
以上三点在高流量的站点是一定要配置的。
4.内容传送网络
每一个发自浏览器的请求都会经过万维主干网穿越整个世界。穿过众多的国家、大洲、大洋到达你的服务器,当然会比较慢。例如:你的服务器在美国,有一个澳大利亚的人通过浏览器访问你的网站,每一个请示都要穿越整个地球到达你的服务器再把得到的数据返回到浏览器。如果你的网站包含大量的静态文件比如:图片、样式表、脚本等,传送和下载这些文件要花费大量的时间。当然,如果你在澳大利亚也有一台服务器并且引导用户去访问这台服务器,会比访问美国的服务器快很多。这样不只是网络响应时间很快而且下载速率也很快,这样静态文件的下载也就会快一些。如果你的网站含有大量的静态内容,这样会大幅提高用户终端的性能。而且,internet 提供商为国家提供比互联网更快的网络服务,因为国家只有少数的为国内internet提供商提供服务的连接可以和国际互联网主干网相连。结果,拥有4M带宽的用户在访问国内的服务器时可以达到4M带宽,但是国外的用户可能只能得到512K的带宽。这就意味着拥有一台国内的服务器可以得到更快的下载速度和响应速度。
在提高用品加载速度的同时,CDN也会减少服务器的负载。由于缓存的原因,你的web服务器请求就大量减少。伴随请求的减少,服务器就会留出大量的资源来处理动态的进程。由于不用再为大量的静态数据做日志,同样会节约大量的日志空间。如果你的网站里有大量的图片、样式、脚本,那么每天你都可以节约数以G计的日志空间。
CDN是由internet相联的联网工作系统,计算机透明地合作传递数据到终端用户,CDN节点布置在多个位置,通常是在主干网上。这些节点相互合作为用户把请求转化成内容,它们也在后台明显地传递内容来优化传递过程。CDN服务器会聪明的选择最近的节点。它会用最快的方式使用户连接到具有用户查找内容的节点上。CDN会测量不同国家的节点数量和多余的节点连接数量。最著名的CDN有Akamai,一些大公司比如微软等都在用它。当然价格也是十分昂贵的。
比较好的方法是把静态文件存到你的网站里但是要用域名做指向。你可以把你的内容存在网站二级域名指向的地方,比如:book.hotbook.cn。你把这个子域名指向CDN的命名服务器,比如:cache.somecdn.net,当浏览器访问book.hotbook.cn的时间,DNS会查找CDN命名服务器。命名服务器会返回给你提供最快访问速度的最近的CDN节点的IP地址。这时浏览器开始发送文件请求到CDN节点,当CDN节点接收到请求之后,它会检查这些数据是否存在缓存中,如果存在它直接从本地把数据发送出去。如果不存在,它会向你的服务器发送请求然后分析请求的缓存头,通过缓存头,它可以确定自己缓存的时效,与此同时,浏览器不会等待CDN取得数据并返回自己。当CDN更新缓存的时候,浏览器会直接从服务器取得数据。在大多时候,CDN只是做为一个代理,拦截每一个请求,使用最快最优化的方式得到没有缓存到的数据,这就是CDN的优势所在。
5.在浏览器中缓存AJAX请求
浏览器可以缓存图片、样式表、脚本到本地硬盘上,同样也可以缓存GET方式请求的XML HTTP请求。缓存是建立在网址的基础上的。当再次请求的时候,如果是同一个URL并且已经在缓存中存在,那么只从缓存里取出数据而不必去服务器请求。理论上,浏览器可以缓存所有的GET请求和返回所有基于URL的缓存数据。如果你使用了一个GET方式的HTTP请求,并且服务器返回一些通知浏览器进行数据缓存的头部信息,那么在以后的请求时,回应会非常快的从缓存中返回,这样就节约了网络传输和下载的延时。
我们缓存了用户的数据,所以在接下来的几天中如果用户再次访问,用户就会得到一个来自浏览器缓存而不是来自服务器的页面。这样第二次访问就会变得非常快。我们也将一些基于用户动作而出现的页面上的小部分缓存起来,当用户执行相同动作的时候,缓存的结果会立即显示出来,这样就节约了网络访问时间。提供给用户一个加载迅速访问迅速的网站。速度感觉会成倍增加。
方法是对Atlas Web service 的GET请求加一些特殊的HTTP头部信息让浏览器在一定的时间内缓存数据。如果你在响应中加上过期的头部信息,浏览器就会缓存这些数据。有两个头部信息需要返回来通知浏览器缓存数据:
HTTP/1.1 200 OK
Expires: Fri, 1 Jan 2030
Cache-Control: public
上面的数据表示让浏览器缓存数据到2030年1月1号。如果你这么设置的话,在过期之前都不会去服务器请求数据。当然还有更多的方法缓存控件。例如:通知浏览器缓存数据60秒钟那么60秒后会重新加载数据。当浏览器的本地缓存过期时会返回缓存响应保护代理。
HTTP/1.1 200 OK
Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60
让我们在web 服务中构造一些这样的头信息:
[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();
}
下面是得到的响应结果:
过期设置是正确的,但是问题出在Cache-control上面。max-age 设置为0会阻止浏览器做任何的缓存处理,如果你想禁止缓存的话可以这么做。看起来相反的事情发生了。
输出通常是不对的,而且没有进行缓存:
不可以更改
max-age
是asp.net 2.0的一个bug,由于max-age 设置为0,ASP.NET 2.0 把 Cache-control
设置成私有的,因为这意味着不使用任何的缓存。这样我们就没有办法让asp.net 2.0返回合适的缓存响应数据的头部信息。这是由于asp.net 2.0 ajax 框架拦截web服务请求并且在响应请求之前默认错误的把max-age 设置为0引起的。通过反编译HttpCachePolicy 类,得到以下代码:
不知为什么,this._maxAge 被设置为0,但是这个判断"if (!this._isMaxAgeSet || (delta < this._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();
}
它会返回下面的头信息
这样max-age 被设置成了60秒,浏览器也可以缓存60秒的数据了。如果60秒内你进行同样的请求,会返回同样的结果。下面的测试结果显示了从服务器返回的时间:
1分钟后缓存过期,浏览器又可以请求到服务器,下面是客户端代码:
function testCache()
{
TestService.CachedGet(function(result)
{
debug.trace(result);
});
}
还有一个问题要解决,在web.config里,asp.net ajax增加了:
<system.web>
<trust level="Medium"/>
这样会阻止我们用反射的方式设置_maxAge 的值,所以我们应该除去它或者把它设置为Full.
<system.web>
<trust level="Full"/>