使用 ASP.NET 编写 Web 应用程序的简单程度令人不敢相信。正因为如此简单,所以很多
开发人员就不会花时间来设计其应用程序的结构,以获得更好的性能了。在本文中,我将
讲述 10 个用于编写高性能 Web 应用程序的技巧。但是我并不会将这些建议仅局限于
ASP.NET 应用程序,因为这些应用程序只是 Web 应用程序的一部分。本文不作为对 Web
应用程序进行性能调整的权威性指南 ― 一整本书恐怕都无法轻松讲清楚这个问题。请将
本文视作一个很好的起点。
成为工作狂之前,我原来喜欢攀岩。在进行任何大型攀岩活动之前,我都会首先仔细查看
指南中的路线,阅读以前游客提出的建议。但是,无论指南怎么好,您都需要真正的攀岩
体验,然后才能尝试一个特别具有挑战性的攀登。与之相似,当您面临修复性能问题或者
运行一个高吞吐量站点的问题时,您只能学习如何编写高性能 Web 应用程序。
我的个人体验来自在 Microsoft 的 ASP.NET 部门作为基础架构程序经理的经验,在此期
间我运行和管理 www.ASP.NET,帮助设计社区服务器的结构,社区服务器是几个著名
ASP.NET 应用程序(组合到一个平台的 ASP.NET Forums、.Text 和 nGallery)。我确信
有些曾经帮助过我的技巧对您肯定也会有所帮助。
您应该考虑将应用程序分为几个逻辑层。您可能听说过 3 层(或者 n 层)物理体系结构
一词。这些通常都是规定好的体系结构方式,将功能在进程和/或硬件之间进行了物理分离
。当系统需要扩大时,可以很轻松地添加更多的硬件。但是会出现一个与进程和机器跳跃
相关的性能下降,因此应该避免。所以,如果可能的话,请尽量在同一个应用程序中一起
运行 ASP.NET 页及其相关组件。
Figure 2 Paging Through the Orders Table
CREATE PROCEDURE northwind_OrdersPaged
(
@PageIndex int,
@PageSize int
)
AS
BEGIN
DECLARE @PageLowerBound int
DECLARE @PageUpperBound int
DECLARE @RowsToReturn int
-- First set the rowcount
SET @RowsToReturn = @PageSize * (@PageIndex + 1)
SET ROWCOUNT @RowsToReturn
-- Set the page bounds
SET @PageLowerBound = @PageSize * @PageIndex
SET @PageUpperBound = @PageLowerBound + @PageSize + 1
-- Create a temp table to store the select results
CREATE TABLE #PageIndex
(
IndexId int IDENTITY (1, 1) NOT NULL,
OrderID int
)
-- Insert into the temp table
INSERT INTO #PageIndex (OrderID)
SELECT
OrderID
FROM
Orders
ORDER BY
OrderID DESC
-- Return total count
SELECT COUNT(OrderID) FROM Orders
-- Return paged results
SELECT
O.*
FROM
Orders O,
#PageIndex PageIndex
WHERE
O.OrderID = PageIndex.OrderID AND
PageIndex.IndexID > @PageLowerBound AND
PageIndex.IndexID < @PageUpperBound
ORDER BY
PageIndex.IndexID
ASP.NET 是您的表示层(或者说应该是您的表示层);它由页、用户控件、服务器控件(H
ttpHandlers 和 HttpModules)以及它们生成的内容组成。如果您具有一个 ASP.NET 页,
它会生成输出(HTML、XML、图像或任何其他数据),并且您针对每个请求运行此代码时,
它都会生成相同的输出,那么您就拥有一个可用于页输出缓存的绝佳备选内容。
就可以高效地为此页生成一次输出,然后对它进行多次重用,时间最长为 60 秒,此时该
页将重新执行,输出也将再一次添加到 ASP.NET 缓存。通过使用一些低级程序化 API 也
可以完成此行为。对于输出缓存有几个可配置的设置,如刚刚讲到的 VaryByParams 属性
。VaryByParams 刚好被请求到,但还允许您指定 HTTP GET 或 HTTP POST 参数来更改缓
存项。例如,只需设置 VaryByParam="Report" 即可对 default.aspx?Report=1 或
default.aspx?Report=2 进行输出缓存。通过指定一个以分号分隔的列表,还可以指定其
他参数。
很多人都不知道何时使用输出缓存,ASP.NET 页还会生成一些位于缓存服务器下游的
HTTP 标头,如 Microsoft Internet Security and Acceleration Server 或 Akamai 使
用的标头。设置了 HTTP 缓存标头之后,可以在这些网络资源上对文档进行缓存,客户端
请求也可在不必返回原始服务器的情况下得以满足。
如果您未运行 IIS 6.0 (Windows Server? 2003),那么您就错过了 Microsoft Web 服务
器中的一些很好的性能增强。在技巧 7 中,我讨论了输出缓存。在 IIS 5.0 中,请求是
通过 IIS 然后进入 ASP.NET 的。涉及到缓存时,ASP.NET 中的 HttpModule 会接收该请
求,并返回缓存中的内容。
如果您正在使用 IIS 6.0,就会发现一个很好的小功能,称为内核缓存,它不需要对
ASP.NET 进行任何代码更改。当请求由 ASP.NET 进行输出缓存时,IIS 内核缓存会接收缓
存数据的一个副本。当请求来自网络驱动程序时,内核级别的驱动程序(无上下文切换到
用户模式)就会接收该请求,如果经过了缓存,则会将缓存的数据刷新到响应,然后完成
执行。这就表示,当您将内核模式缓存与 IIS 和 ASP.NET 输出缓存一起使用时,就会看
到令人不敢相信的性能结果。在 ASP.NET 的 Visual Studio 2005 开发过程中,我一度是
负责 ASP.NET 性能的程序经理。开发人员完成具体工作,但是我要看到每天进行的所有报
告。内核模式缓存结果总是最有意思的。最常见的特征是网络充满了请求/响应,而 IIS
运行时的 CPU 使用率只有大约 5%。这太令人震惊了!当然使用 IIS 6.0 还有一些其他原
因,但是内核模式缓存是其中最明显的一个。
技巧 9 ― 使用 Gzip 压缩
虽然使用 gzip 并不一定是服务器性能技巧(因为您可能会看到 CPU 使用率的提高),但
是使用 gzip 压缩可以减少服务器发送的字节数量。这就使人们觉得页速度加快了,并且
还减少了带宽的用量。根据所发送数据、可以压缩的程度以及客户端浏览器是否支持(IIS
只会向支持 gzip 压缩的客户端发送经过 gzip 压缩的内容,如 Internet Explorer
6.0 和 Firefox),您的服务器每秒可以服务于更多的请求。实际上,几乎每当您减少所
返回数据的数量时,都会增加每秒请求数。
我为您讲述了一些我认为在编写高性能 ASP.NET 应用程序时有所帮助的技巧。正如我在本
文前面部分提到的那样,这是一个初步指南,并不是 ASP.NET 性能的最后结果。(有关改
善 ASP.NET 应用程序性能的信息,请参阅 Improving ASP.NET Performance。)只有通过
自己的亲身体验才能找出解决具体性能问题的最好方法。但是,在您的旅程中,这些技巧
应该会为您提供一些好的指南。在软件开发中,几乎没有绝对的东西;每个应用程序都是
唯一的。