asp.net的10个提升性能或扩展性的秘密(二)

简介


Asp.net有很多值得你挖掘的“秘密”,当你发现了它们,将会给你网站的性能和可扩展性带来巨大提升!例如,对于Membership以及Profile提供程序有一些秘密的瓶颈,它们很容易被解决,从而使认证和授权更加快速。另外,asp.net的http管线可以为每一个请求作处理,防止执行了某些不必要的代码而遭受攻击。不只是这些,asp.net工作进程能够突破默认限制,从而完全发挥它的威力。在浏览器端(不是在服务器端)的页面分段输出缓存能显著减少由于请求访问所需要占用的大量下载时间。在需要的用户界面上的加载能够给你的网站带来快速而平稳的体验。最后,内容分发网络(CDN)以及对HTTP缓存头的正确使用能够使你的网站得到快速响应。

在这篇文章中,你将学习这些技术,给你的asp.net应用程序性能和可扩展性带来巨大的提升。下面是接下来将要讨论的技术:

  • Asp.net管线优化
  • Asp.net进程配置优化
  • Asp.net网站上线之前你必须要做的事
  • 内容分发网络(CDN)
  • 浏览器端缓存Ajax调用
  • 最大限度地正确使用浏览器缓存
  • 在需要的UI上逐步加载以提供快速平滑的体验
  • 优化asp.net 2.0Profile 提供程序
  • 怎样在不“下线”站点的情况下查询asp.net 2.0的Membership表
  •  防止拒绝服务攻击
注:以上只有黑色字体的项被翻译!

上面的技术都能够在任何的asp.net网站中实现,特别是那些使用了asp.net 2.0的Membership以及Profile 提供程序的网站。


最大限度地正确使用浏览器缓存


使用统一的URL

浏览器基于内容缓存URL。当URL发生改变时,浏览器会从服务器获取该URL的一个新版本。URL可以通过查询参数来改变。例如,/default.aspx被缓存在客户端浏览器,如果你请求/default.aspx?123,它将从服务器重新获取内容。如果你正确地设置了用于缓存的响应头,该请求的响应也能够被缓存到浏览器。在那种情况下,改变请求参数,都将从服务器返回新的内容。所以,当你想获得缓存的响应时你需要确信你在各处都使用了统一的URL。一个通常的错误是,有时www子域会被URL忽略。比如,www.pageflakes.com/default.aspx是不同于pageflakes.com/default.aspx的。它们将会被分开缓存。

更长时间地缓存静态内容

静态文件可以被缓存更长时间,比如一个月。如果你觉得你应该缓存两天,这样当你改变文件时,用户将在不久能获取到,你的想法是错误的!如果你更新了一个通过Expires头缓存的文件,新用户将可以直接获取最新的文件,而老用户在你本地浏览器失效日期之前,将一直只能看到老文件。所以,一旦你正在使用Expires头来缓存静态文件,你应该使用一个尽可能大的值。

例如,如果你已经设置了一个Expires头来缓存一个文件三天,一个用户今天获得了文件,并且在接下来的三天内它都将存储在缓存中。另一个用户在明天获得了该文件,并缓存它在明天过后的三天。如果你在后天修改了文件,第一个用户将在第四天看到,第二个用户将在第五天看到。所以,不同的用户将看到不同版本的文件。结果是,你不需要通过设置一个很小的失效日期来使得所有的用户都在不久之后获得新版本。你将不得不修改文件的URL来确保每一个用户立刻获得相同的最新文件。

你可以通过IIS 管理器来设置静态文件的Expires头。在后面你将学到如何来设置。

使用缓存友好的文件目录结构

将缓存内容存储在一个公共目录下。例如,存储你站点所有的图片到static文件来代替将图片分开放置到不同的子目录下。这将有助于你使用统一的URL。例如,在任何地方你都可以这样写:/static/images/somefile.gif。

重用公共图片文件

有时,我们将公共的图片文件放在几个不同的虚拟目录中,这样我们能够写很短的路径来引用文件。例如,你在根目录、一些子目录和CSS文件夹下都有一个indicator.gif。你这么做因为你不需要当心不同的路径,并且你可以仅仅使用文件名来作为虚拟URL。但,这很不利于缓存。每一个这种文件的拷贝在浏览器上都是被独立缓存的。所以,你应该收集解决方案内所有的图片文件,然后将它们放置到一个相同的文件中,并使用相同的路径来引用它们。

当你希望缓存失效时,改变文件名称

当你希望改变一个静态文件时,不要仅仅更新文件,因为它已经被缓存到用户的浏览器中去了。你需要改变文件的名称,并改变对其的所有引用,来确保浏览器下载新文件。你也可以在数据库中存储文件名,或者存储在配置文件中然后使用数据动态绑定到URL中(比如在请求后面追加版本号)。

在访问静态文件时使用一个版本号

如果你不想你的文件因为存在同一文件的多个拷贝而杂乱,你可以使用查询参数来对相同的文件标识不同的版本。例如,一个GIF文件可以被这样访问:/static/images/indicator.gif?v=1。当你想改变这个indicator.gif文件时,你可以覆盖相同的文件,然后更新所有对该文件的引用为:/static/images/indicator.gif?v=2。这种方式你可以再次更新相同的文件,并且仅需要更新版本号。

存储可缓存的文件到一个不同的域名下

将静态文件放到一个不同的域名下总是一个不错的注意。首先,浏览器能两个并发的连接来下载静态文件。另一个好处是你不需要给静态文件发送cookies。当你将静态文件作为你Web应用程序的一部分并和其他文件放置在同一个域名下,浏览器需要给它发送所有的asp.net Cookies以及你Web 应用程序产生的所有其他的cookies。这使得请求头部变得很大(其实没有必要)并且浪费了带宽。你不需要发送那些cookies来访问这些静态文件。所以,如果你将这些静态文件放置到另一个域名下,那些cookies将不会发送。例如,将静态文件放置在www.staticcontent.com域名下,而你的Web站点运行在www.dropthings.com。这里所谓的其他的域名,并不一定需要是另外一个完整的web站点。它可以仅仅是和当前Web 应用程序共享路径的一个别名。

SSL不能被缓存,所以最小程度地使用SSL

任何涉及到SSL的内容都不能被缓存。所以,你需要将那些静态文本放置到SSL外部。另外,你应该尝试限制只在那些安全的页面(例如登陆页面,付款页面)上使用SSL。站点其他的页面采用通常的HTTP请求。SSL会加密请求与相应内容,并因此给服务器带来额外负载。加密内容也比通常文件的内容更大并因此在传输过程中会占用更多的带宽。

HTTP POST请求将永远无法缓存

缓存仅仅发生在HTTP GET 请求上。HTTP POST 请求永远不会被缓存。所以,你想缓存的任何形式的AJAX调用都需要是HTTP GET形式的请求。

生成Content-Length响应头部

当你正在通过Web Service或者HTTP Handlers动态产生内容时,确保你设置了Content-Length头部。当浏览器通过查看Content-Length头部知道需要下载多少字节时,它能够对更快速地下载数据有多种优化方式。当这个头是当前连接的头部时,浏览器能够更有效地使用持续连接。这可以避免浏览器为每一个请求都打开一个新的连接。当没有Content-Length头部时,浏览器不知道它将从服务器接受多少字节数据,因此连接一直被打开着,从服务端获取数据直到连接被关闭。因此,你错失了“持续”连接的优势,它能够大大地减少对一些像CSS、JS、图片等小文件的下载时间。(关于持续连接,请查看我之前的Comet系列的文章)


怎样在IIS上配置静态文件缓存


在IIS管理器上,Web站点属性窗口有个”HTTP 头”的选项卡,在那里你可以为所有的IIS处理的请求定义Expires头。你可以定义是否直接让内容失效或者在一定的天数或者特定的日期之后失效。第二个选项(Expires after)使用的是“滑动过期”,而不是“绝对过期”。这非常得有用,因为它作用在每一个请求上。无论什么时候,某个人请求一个静态文件,IIS将基于该项计算失效日期。


对于那些被asp.net处理的动态页面,通过handler可以修改Expires头部并且覆写IIS默认的设置。


基于需求的页面渐进加载以提升平滑的用户体验


一个好的解决方案是,动态加载HTML片段以及需要的Javascript。在dropthings这个项目中,我在下面的截图中显示了如何做它:


当你点击“Help”链接的时候,它动态加载帮助的内容。这段HTML不是作为default.aspx页面的一部分来解析的。因此和help相关的内容不影响站点本身的加载性能。而仅仅当用户点击“Help” 链接时才会动态加载。当用户再次点击”Help”链接,它可以直接从浏览器的缓存里读取。

它的原理是向一个*.aspx页面发出一个XMLHttp调用,获得响应的html标签,将这段html代码加入到一个DIV容器中,并让DIV可见。

用这种方法,你可以将UI内容分拆到许多更小的*.aspx文件中。尽管这些文件不能含有javascript或者stylesheet块,但是它们可以包含大量的你需要按需展示的html代码。因此你可以让只需要加载最基本的内容。


应对拒绝服务攻击


Web Service是黑客最容易攻击的目标,因为甚至一个初级黑客都可能通过直接调用那些很耗费资源的Web Service而挂掉一台服务器。Ajax起始页是DOS最好的攻击目标。因为如果你只是访问主页而不保护cookies,每一个攻击都建立一个新的用户,请求一个新页面。首次访问是最消耗资源的一次。但,它是挂掉一个网站最容易攻击的方式。你可以自己尝试,仅需要写如下的简单代码:


给你的最大的惊喜是,你将发现,在两次调用之后,你不能得到有效的响应。但这不是你表明你已经成功得挂掉了服务器。这只是你的请求被拒绝了。你很高兴你不再能获取任何服务,因此你实现了拒绝服务(你自己这么认为的)。而我们会高兴拒绝为你服务。

我的做法很简单,我会记录一个IP发送了多少次请求。当请求的数量超过一个特殊值时,拒绝一定时间内的进一步请求。方案是在asp.net的Cache中记录调用者的IP并且维护对每一个发起请求的统计。当一个IP的统计值超出限制,拒绝该IP更之后的请求,在10分钟之内。在十分钟之后,再次响应来自该IP的请求。

我有一个类叫做ActionValidator,它维护了一些特殊操作的统计,例如:首次访问,再次访问,异步回传,添加新部件,添加新页面等等。它检测是否为该特定IP的具体操作数超过了限制值。


枚举包含了操作的类型,以及检测它们的限制值,并设置了一个特定的再次接受服务的时间。

一个静态方法,IsValid做具体的检测处理。如果请求没有超过限制值,它返回true,否则如果请求需要被拒绝,返回false。你也可以显示一个页面,显示结果。


缓存的键是通过操作类型与客户端IP地址共同构建的。首先,它检测在缓存中是否有关于该操作与IP的实体。如果没有,开始计数并且为该操作记录其IP到缓存中。该缓存项的绝对失效时间可以确保在缓存项持续时间之后将被清除并且重新开始计数。当已经有了一个实体在缓存中时,获取最后一次访问统计,并且检查是否有超出限制值。如果没有超过,增加计数。通过做:Cache[url]=hit;,不需要再次存储更新过的值到缓存中。因为修改的是hit对象的引用,更新它就意味着也更新了缓存里的对象本身。事实上,如果你在缓存中再存储一次,cache失效计数将重新计数并且失去了在特殊持续时间后重新计数的功能。

使用非常简单,在default.aspx页面:


我在这里检查了特殊的场景,例如首次访问,再次访问,回发等等。

当然,你可以加入一些思科防火墙以防止拒绝服务攻击。你将得到你的托管服务提供商的一个保证整个网络中对DOS以及DDOS(分布式DOS)攻击是免疫的。但这些应用程序级别的DOS攻击是硬件无法阻止的,它必须在你自己的代码中实现。

只有非常少的部分网站采用了这种措施来防止应用程序级别的DOS攻击。因此,通过写一个简单的循环,访问很耗资源的网页或者以你家庭宽带不断调用Web Service,可以很轻易地挂掉一个服务器。


posted @ 2011-10-13 21:29  程序员天下  阅读(296)  评论(1编辑  收藏  举报