改进性能和样式的 24个 ASP 技巧第1/2页
程序员文章站
2022-06-15 14:21:37
祥细内容: 简介 技巧 1:在 web 服务器上缓存常用数据 技巧 2:在 application...
祥细内容:
简介
技巧 1:在 web 服务器上缓存常用数据
技巧 2:在 application 或 session 对象中缓存常用数据
技巧 3:在 web 服务器磁盘上缓存数据和 html
技巧 4:避免在 application 或 session 对象中缓存非灵活组件
技巧 5:不要在 application 或 session 对象中缓存数据库连接
技巧 6:妙用 session 对象
技巧 7:在 com 对象中封装代码
技巧 8:晚点获取资源,早点释放资源
技巧 9:进程外的执行将牺牲可靠性
技巧 10:显式使用选项
技巧 11:在子例程和函数中使用局部变量
技巧 12:将常用数据复制到脚本变量
技巧 13:避免重新定义数组
技巧 14:使用响应缓冲
技巧 15:批处理内嵌脚本和 response.write 语句
技巧 16:在开始长时间的任务之前先使用 response.isclientconnected
技巧 17:使用 <object> 标记实例化对象
技巧 18:使用 ado 对象和其他组件的 typelib 绑定
技巧 19:利用浏览器的验证能力
技巧 20:在循环中避免字符串串联
技巧 21:启用浏览器和代理缓存
技巧 22:尽可能使用 server.transfer 替代 response.redirect
技巧 23:在目录 url 尾部加斜线
技巧 24:避免使用服务器变量
--------------------------------------------------------------------------------
简介
性能是一个特性。您需要预先设计性能,或是在日后重新编写应用程序。换句话说,什么是最大限度优化 active server pages (asp) 应用程序性能的好策略?
本文为优化 asp 应用程序和"visual basic(r) 脚本编辑器 (vbscript)"提供了许多技巧。对许多陷阱和缺陷进行了讨论。本文所列的建议均在 http://www.microsoft.com 及其他站点上进行了测试,而且工作正常。本文假定您对 asp 开发有基本的理解,包括对 vbscript 和/或 jscript、asp application、asp session 和其他 asp 内部对象(请求、响应和服务器)。
asp 的性能,通常不止取决于 asp 代码本身。我们并不想在一篇文章中囊括所有的至理名言,只在最后列出与性能相关的资源。这些链接包括 asp 和非 asp 主题,包括"activex(r) 数据对象 (ado)"、"部件对象模型 (com)"、数据库和"internet 信息服务器 (iis)"配置。这些是我们喜欢的链接 - 务请关注它们。
技巧 1:在 web 服务器上缓存常用数据
典型的 asp 页从后端数据库检索数据,然后将结果转换为超文本标记语言 (html)。无论数据库的速度如何,从内存检索数据要比从后端数据库检索数据快得多。从本地硬盘读取数据通常也要比从数据库检索数据快得多。因此,通常可以通过在 web 服务器(在内存或磁盘)上缓存数据来改善性能。
缓存是典型的空间与时间的折衷。如果恰当地缓存数据,您将看到性能会有惊人的提高。为使缓存发挥效力,它必须保持经常重用的数据,而且重新计算这些数据的代价是昂贵的或比较昂贵的。如果缓存充满了垃圾数据,则是对存储器的浪费。
不经常变化的数据也是缓存的候选数据,因为您无须担心数据与数据库的同步问题。组合框、引用表、dhtml 碎片、可扩展标记语言 (xml) 字符串、菜单项和站点配置变量(包括数据源名称 (dsn)、internet 协议 (ip) 地址和 web 路径)都是缓存的候选数据。注意,您可以缓存数据的表示而不是数据本身。如果 asp 页不经常更改,而且缓存的成本也非常高(例如,整个产品目录),请考虑预先生成 html,而不是在每次请求时重新绘制。
数据应缓存在何处,有哪些缓存策略?数据经常缓存在 web 服务器内存或 web 服务器磁盘上。下面两个技巧讨论这些选项。
技巧 2:在 application 或 session 对象中缓存常用数据
asp application 和 session 对象为在内存中缓存数据提供了方便的容器。既可以将数据赋予 application 对象,也可将数据赋予 session 对象,这些数据在 http 调用中将保留在内存中。session 数据按用户存储,而 application 数据在所有用户间共享。
何时将数据载入 application 或 session?通常,在 application 或 session 启动时加载数据。要在 application 或 session 启动时加载数据,请在下面两函数中添加相应的代码:
application_onstart()
或
session_onstart()
。这两个函数应该位于 global.asa;如果没有,可以添加这些函数。也可以在第一次需要数据时加载数据。要进行上述操作,请在 asp 页中添加一些代码(或编写可重用的脚本函数),这些代码检查数据是否存在,并在数据不存在时加载数据。这是称为迟缓计算的经典性能技术的例子 - 在您的确需要它之前,不进行计算。请看例子:
<%
function getemploymentstatuslist
dim d
d = application("employmentstatuslist")
if d = "" then
' fetchemploymentstatuslist 函数(不显示)
' 从 db 中取出数据,返回数组
d = fetchemploymentstatuslist()
application("employmentstatuslist") = d
end if
getemploymentstatuslist = d
end function
%>
可以为每一块所需的数据编写类似的函数。
数据应该以什么格式存储?任何变量类型均可存储,因为所有脚本变量是各不相同的。例如,可以存储字符串、整型或数组。通常,您将以这些变量类型之一存储 ado 记录集的内容。若要获取 ado 记录集衍生的数据,可以手工将数据复制到 vbscript 变量中,每次一个字段。使用一个 ado 记录集保留函数 getrows()、getstring() 或 save() (ado 2.5),会更快更简便。完整而详细的内容已超出了本文的范围。下面的演示函数使用了
getrows()
来返回记录集数据的数组:
' 取记录集,以数组返回
function fetchemploymentstatuslist
dim rs
set rs = createobject("adodb.recordset")
rs.open "select statusname, statusid from employeestatus", _
"dsn=employees;uid=sa;pwd=;"
fetchemploymentstatuslist = rs.getrows() ' 以数组返回数据
rs.close
set rs = nothing
end function
对上面示例的进一步改进应当是缓存该列表的 html,而不是缓存数组。下面是一个简单的范例:
' 取记录集,以"html 选项"列表返回
function fetchemploymentstatuslist
dim rs, fldname, s
set rs = createobject("adodb.recordset")
rs.open "select statusname, statusid from employeestatus", _
"dsn=employees;uid=sa;pwd=;"
s = "<select name=""employmentstatus">" & vbcrlf
set fldname = rs.fields("statusname") ' ado 字段绑定
do until rs.eof
' 下面一行违背了不要进行字符串连接,
' 但这是可以的,因为我们正在建立高速缓存
s = s & " <option>" & fldname & "</option>" & vbcrlf
rs.movenext
loop
s = s & "</select>" & vbcrlf
rs.close
set rs = nothing ' 参见尽早释放
fetchemploymentstatuslist = s ' 以字符串返回数据
end function
在正常的情况下,可以在 application 或 session 作用域中缓存 ado 记录集本身。有两个警告:
ado 必须为标记的*线程
必须使用断开连接的记录集。
如果不能保证满足这两个要求,请不要缓存 ado 记录集。在下面的非灵活组件和不要缓存连接技巧中,我们将讨论在 application 或 session 作用域中存储 com 对象的危险。
如果在 application 或 session 作用域中存储数据,这些数据将一直保留在那儿,直到在程序中改变它、session 过期或 web 应用程序重新启动时为止。数据需要更新如何处理?若要用手工强制更新应用程序数据,可以调用只允许管理员访问的数据更新 asp 页。另外,还可以通过函数,周期地自动刷新数据。下面的示例存储带缓存数据的时间戳,在指定时间间隔后刷新数据。
<%
' 未显示错误处理...
const update_interval = 300 ' 刷新时间间隔,以秒计
' 函数返回雇佣状态列表
function getemploymentstatuslist
updateemploymentstatus
getemploymentstatuslist = application("employmentstatuslist")
end function
' 定期更新缓存的数据
sub updateemploymentstatuslist
dim d, strlastupdate
strlastupdate = application("lastupdate")
if (strlastupdate = "") or _
(update_interval datediff("s", strlastupdate, now)) then
' 注意:此处可能有两个或多个调用。这是可以的,只不过
' 产生几个不必要的取指令罢了(就此有一个工作区)
' fetchemploymentstatuslist 函数(不显示)
' 从 db 中取数据,返回一个数组
d = fetchemploymentstatuslist()
' 更新 application 对象。用 application.lock()
' 来确保一致的数据
application.lock
application("employmentstatuslist") = d
application("lastupdate") = cstr(now)
application.unlock
end if
end sub
其他示例,请参阅具有 application 数据的最快列表框(英文)。
请注意,在 session 或 application 对象中缓存大型数组并非上策。在访问数组元素之前,脚本语言的语法要求建立整个数组的临时副本。例如,如果在 application 对象中缓存了将美国邮政编码映射到本地气象站的字符串数组,该字符串数组有 100,000 个元素,asp 在找出一个字符串之前,必须将所有 100,000 个气象站复制到临时数组中。在这种情况下,建立带自定义方法的自定义组件,来存储气象站 - 或使用一个字典组件,也许更好。
请不要在倒洗澡水时把孩子一同倒掉,对这种观点的一个新的注解是:数组提供了对内存中相邻关键-数据对的快速查找和存储。索引字典比索引数组要慢。您应该根据具体情况选择能够提供最佳性能的数据结构。
技巧 3:在 web 服务器磁盘上缓存数据和 html
有时,数据过多不能在内存中进行缓存。"过多"是一种定性的判断;它取决于打算消耗的内存量,还有缓存项的数量和这些项的检索频率。总之,如果有过多的数据要在内存中缓存,请考虑以文本或 xml 文件的形式,在 web 服务器的硬盘上缓存数据。可以将在磁盘上缓存数据和在内存中缓存数据组合起来,为站点建立最优的缓存策略。
注意,在度量单个 asp 页的性能时,在磁盘上检索数据不一定比从数据库中检索数据快。但是,缓存减轻了数据库和网络的负荷。在高负荷情况下,这将明显提高总体通信量。在查询成本很高时缓存查询的结果,缓存便非常有效,例如多表联合或复杂的存储过程,或缓存大型的结果集。按照惯例,测试竞争方案。
asp 和 com 提供了几种构建磁盘缓存方案的工具。ado 记录集的 save() 和 open() 函数,保存和加载磁盘上的记录集。您可以使用这些方法重写上面 application 数据缓存技巧中的范例代码,用 save() 文件替换向 application 对象写入数据的代码。
还有其他一些处理文件的组件:
scripting.filesystemobject 使您能够创建、读取和写入文件。
msxml 是随 internet explorer 提供的 microsoft(r) xml 解析器,它支持保存和加载 xml 文档。
lookuptable 对象(在 msn 上使用的范例)是从磁盘加载简单列表的良好选择。
最后,请考虑在磁盘上缓存数据的表示,而不是数据本身。预制的 html 可以作为 .htm 或 .asp 文件存储在磁盘上;超级链接可以直接指向这些文件。可以使用商业工具,如 xbuilder 或 microsoft(r) sql server 的 internet 发行功能来自动化 html 生成过程。另外,可以将 html 片段 #include 到 .asp 文件。还可以使用 filesystemobject 从磁盘读取 html 文件或使用 xml 进行早期调整(英文)。
技巧 4:避免在 application 或 session 对象中缓存非灵活组件
虽然在 application 或 session 对象中缓存数据是个好主意,但是缓存 com 对象可能有严重缺陷。将常用 com 对象嵌入 application 或 session 对象通常具有吸引力。遗憾的是,很多 com 对象,包括用 visual basic 6.0 或更早版本编写的 com 对象,在 application 或 session 对象中存储时将导致严重的瓶颈。
特别是任何非灵活组件,在 session 或 application 对象中缓存时将导致性能瓶颈。灵活组件是标记为
threadingmodel=both
的组件(它聚集了*线程汇集器 (ftm))或标记为
threadingmodel=neutral
的组件(windows(r) 2000 和 com+ 中新增的"中性"模型。)下列组件是非灵活的:
*线程组件(除非它们聚集了 ftm)。
单元线程组件。
单线程组件。
已配置组件(microsoft transaction server (mts)/com+ 库和服务器包/应用程序)为非灵活组件,除非它们是"中性"线程的。单元线程组件和其他非灵活组件最适于在页作用域工作(也就是说,它们在单个 asp 页上创建和销毁)。
在 iis 4.0 中,标记为
threadingmodel=both
的组件被视为灵活的。在 iis 5.0 中,这已经不够了。组件不仅必须标记为 both,而且还必须聚集 ftm。灵活性文章说明了如何使得用"活动模板库"编写的 c++ 组件聚集 ftm。请注意,如果组件缓存接口指针,这些指针本身必须为灵活的、或者必须存储在"com 全局接口表 (git)"中。如果不能重新编译 both 线程组件,使它聚集 ftm,则可以将该组件标记为
threadingmodel=neutral
。另外,如果不希望 iis 进行灵活性检查(这样,希望非灵活组件能够存储在 application 或 session 作用域中),可以在 metabase 中设置
asptrackthreadingmodel
为
true
。不主张更改
asptrackthreadingmodel
。
如果试图在 application 对象中存储用
server.createobject
创建的非灵活组件,iis 5.0 将产生错误。可以通过在 global.asa 中使用
<object runat=server scope=application ...>
解决该问题,但是不主张这样做,因为这将导致汇集和串行化,说明如下。
如果缓存非灵活组件,会发生什么错误呢?缓存在 session 对象中的非灵活组件,将把会话"锁定"到某个 asp 工作器线程。asp 维护着一个工作器线程池,它向请求提供服务。通常,新的请求由第一个可用的工作器线程来处理。如果 session 被锁定到某个线程,则该请求将不得不等待它所关联的线程变为可用。打个比方:您进入一个超市,挑选了一些食品,然后在第 3 号收款台交款。从这以后,每当您在这个超市购买食品,都不得不始终在第 3 号收款台交款,即使是在其他收款台人少或没人时。
将非灵活组件存储在 applicaton 作用域甚至会对性能产生更严重的影响。asp 将不得不创建专用的线程来运行非灵活的、applicaton 作用域内的组件。这将导致两种后果:所有调用不得不被汇集到该线程,而且所有调用被串行化。汇集意味着:参数不得不存储在内存的共享区;对该专用线程执行昂贵的上下文切换;组件的方法被执行;结果汇集到共享区域;以及经过另一个昂贵的上下文切换,使控制权返回原来的线程。串行化意味着所有方法必须一个挨一个地运行(同一时刻只能运行一个方法)。两个不同的 asp 工作器线程不可能同时执行共享组件上的方法。这将扼杀并行机制,尤其是在多处理器计算机上。更坏的是,所有非灵活的、application 作用域内的组件都将共享一个线程("host sta"),所以串行化的影响更加严重。
是否感到困惑?下面我们提出几个通用规则。如果您正在用 visual basic (6.0) 或更早版本编写对象,请不要将它们缓存在 application 或 session 对象中。如果您不知道对象的线程模型,就不要缓存它。不要缓存非灵活对象,而应当在每页上创建并释放它们。对象将直接运行在 asp 工作器线程上,这样,将不会发生汇集或串行化。如果 com 对象正运行在 iis 框中,而且如果它们没有花很长时间来初始化和取消,性能将是足够的。注意,不要用该方法使用单线程对象。小心:vb 可以创建单线程的对象!如果您必须以该方式使用单线程的对象(如 microsoft excel 电子表格),则不要期望有很高的吞吐量。
当 ado 被标记为*线程时,则缓存 ado 记录集是安全的。要将 ado 标记为*线程,请使用 makfre15.bat 文件,该文件通常位于如下目录中:\\program files\common\system\ado。
警告: 如果您正在用 microsoft access 作为数据库,则不应当将 ado 标记为*线程。通常,ado 记录集还必须是断开连接的,如果您不能控制站点的 ado 配置(例如,您是独立的软件厂商 [isv],将 web 应用程序卖给客户,然后由他们来管理他们自己的配置),那么不缓存记录集可能会更好。
词典组件也是灵活对象。lookuptable 从数据文件加载它的数据,并且它对组合框数据和配置信息是有用的。来自 duwamish books 的 pagecache 对象提供了目录语义,和 caprock dictionary 的表现一样。这些对象或它们的派生对象可以构成有效缓存策略的基础。注意,scripting.dictionary 对象不是灵活的,所以不应当存储在 application 或 session 作用域。
技巧 5:不要在 application 或 session 对象中缓存数据库连接
缓存 ado 连接通常是不好的策略。如果一个 connection 对象存储在 application 中,并在所有页上使用,那么所有页将竞争使用该连接。如果 connection 对象存储在 asp session 对象中,那么将为每个用户创建数据库连接。这将连接池的好处毁于一旦,并对 web 服务器和数据库产生不必要的压力。
取代缓存数据库连接的方法是,在每个使用 ado 的 asp 页上创建并取消 ado 对象。这是个有效的方法,因为 iis 具有内置的数据库连接池。更准确的说,iis 自动启用 oledb 和 odbc 连接池。这确保了创建并取消每个页上的连接将是有效的。
由于被连接的记录集中存储有对数据库连接的引用,所以,不应当在 application 或 session 对象中缓存被连接的记录集。但是,可以安全地缓存断开连接的记录集,因为它不包含对其数据连接的引用。要断开记录集的连接,请执行如下两个步骤:
set rs = server.createobject("adodb.recordset")
rs.cursorlocation = aduseclient ' 第 1 步
' 植入带数据的记录集
rs.open strquery, strprov
' 现在断开记录集同数据提供者和数据源的连接
rs.activeconnection = nothing ' 第 2 步
有关连接池的详细信息,请参阅 ado 和 sql server(英文)引用。
技巧 6:妙用 session 对象
在肯定了在 applications 和 sessions 中缓存的优点之后,我们建议您避免使用 session 对象。下面将会谈到,当用于忙碌站点时,sessions 有几个缺点。所谓忙碌,通常是指站点每秒请求数百页或同时有数千个用户。该技巧对于必须进行水平扩展的站点,即那些利用多个服务器来适应负载或执行容错功能的站点来说,更加重要。对于较小的站点,如 intranet 站点,sessions 的便利,与开销相比也是值得的。
为了翻新,asp 自动为每个访问 web 服务器的用户创建一个 session。每个 session 有大约 10 kb 内存开销(在存储在 session 中的任何数据中是最高的),并使所有的请求都慢了一点。session 一直保持活动状态,直到达到可配置的超时(通常 20 分钟)为止。
session 最大的问题不是性能而是可伸缩性。session 不能跨越 web 服务器;一旦在一个服务器上创建了 session,它的数据就保持在那里。这意味着,如果您在 web 领域中使用 sessions,您将不得不为每个用户的请求设计一种策略,以便始终将这些请求引向用户的 session 所在的服务器。这被称为将用户"粘"到 web 服务器上。术语"粘性会话"即来源于此。由于 session 没有保持到磁盘上,所以,当 web 服务器崩溃时,被"粘住"的用户将丢失他们的 sessions 状态。
用于实施粘性会话的策略包括硬件和软件解决方案。如 windows 2000 advanced server 中的网络负载平衡解决方案和 cisco 公司的"本地指向器"解决方案可以实施粘性会话,但以牺牲一些可伸缩性为代价。这些解决方案并不完美。我们不主张您现在全盘推翻您的软件解决方案(我们过去常用 isapi 筛选器和 url 矫直对方案进行检查)。
application 对象也不能跨越服务器;如果您需要在 web 领域内共享并更新 application 数据,则需要使用后端数据库。但只读的 application 数据在 web 领域中仍然有用。
如果只是为了增加正常运行时间(用于处理故障转移和服务器维护),大多数执行重要任务的站点将需要部署至少两台 web 服务器。所以,在设计执行重要任务的应用程序时,您将需要实施"粘性会话",或者简单地避开 sessions 以及其他任何在单个 web 服务器上存储用户状态的状态管理技术。
如果当前没有使用 sessions,请确保将它们关闭。可以通过"internet 服务管理器"(请参阅 ism 文档)来为应用程序执行该操作。如果决定使用 sessions,可以采取几个方法来将对性能的影响降低到最小。
可以将不需要 sessions 的内容(如"帮助"屏幕、访问者区域等)移动到关闭了 sessions 的、单独的 asp 应用程序中。可以逐页提示 asp:在给定的页中您不需要 session 对象;使用位于 asp 页顶端的如下指令:
<% @enablesessionstate=false %>
使用该指令的一个很好的原因是,session 给框架集带来了有趣的问题。asp 保证任何时候只执行一个来自 session 的请求。这样可以确保如果浏览器为一个用户请求了多个页时,在每一时刻只有一个 asp 请求将进入 session;这就避免了在访问 session 对象时出现多线程问题。遗憾的是,结果,框架集中的所有页均被以串行化方式绘制,一个接一个地,而不是同时地。这样,用户可能不得不等待很长时间才能得到所有框架内容。这意味着:如果某些框架页不信任 session,一定要使用
@enablesessionstate=false
指令告诉 asp。
作为使用 session 对象的替代方式,有很多方法可以用来管理 session 状态。对于状态数量较小的情况(不到 4 kb),通常建议使用 cookies、querystring 变量和隐藏形式的变量。对于较大数量的数据,如购物推车,则使用后端数据库是最合适的选择。关于在 web 服务器领域中的状态管理技术已经有很多资料。详细信息,请参阅 会话状态(英文)。
技巧 7:在 com 对象中封装代码
如果您有很多 vbscript 或 jscript,那么您可以通过把代码移动到已编译的 com 对象来经常改进它们的性能。已编译的代码通常比被解释代码运行得更快。已编译的 com 对象可以通过"早期绑定"访问其他 com 对象,这种调用 com 对象方法的手段,比脚本所使用的"后期绑定"更有效。
将代码封装在 com 对象种有如下好处(超越性能):
com 对象是将表达逻辑与业务逻辑分隔开来的好办法。
com 对象启用了代码重用。
很多开发商发现,用 vb、c++ 或 visual j++ 书写的代码,比 asp 更容易调试。
com 对象有一些缺点,包括初始开发时间以及需要不同的编程技巧。需要警告您的是,封装"少"量的 asp 可能会导致性能降低,而不是提高。通常,在少量 asp 代码封装到 com 对象时出现这样的情况。这时候,创建和调用 com 对象的开销,超过了已编译代码的好处。至于 asp 脚本和 com 对象代码怎样合并才能产生最佳性能还有待测试。注意,与 windows nt(r) 4.0/iis 4.0 相比,microsoft 已经在 windows 2000/iis 5.0 中极大地提高了脚本和 ado 性能。这样,已编译代码对 asp 代码的性能优势已经随着 iis 5.0 的引入而降低。
有关在 asp 中使用 com 对象的优缺点的更多讨论,请参阅 asp 组件准则和用 com 和 microsoft visual basic 6.0 对分布式应用程序进行编程(英文)。如果您的确部署了 com 组件,要对它们进行强度测试是非常重要的。实际上,所有 asp 应用程序都应当作为正式过程进行强度测试。
技巧 8:晚点获取资源,早点释放资源
这是个小技巧。通常,最好晚点获取资源而要早点释放资源。这些资源包括 com 对象、文件句柄和其他资源。
ado 连接和记录集是这种优化的首要目标。当您使用完记录集,就是说用它的数据打印完一个表格后,请立即将它释放,而不是等到页的末尾。将您的 vbscript 变量设置为
nothing
是最好的做法。不要让记录集简单地脱离作用域。同时,应当释放任何有关的 command 或 connection 对象。(不要忘了对记录集或"连接"调用
close()
,在将它们设置为
= nothing
之前。)这将缩短数据库必须为您调整资源的时间跨度,并将数据库连接尽可能快地释放给连接池。
简介
技巧 1:在 web 服务器上缓存常用数据
技巧 2:在 application 或 session 对象中缓存常用数据
技巧 3:在 web 服务器磁盘上缓存数据和 html
技巧 4:避免在 application 或 session 对象中缓存非灵活组件
技巧 5:不要在 application 或 session 对象中缓存数据库连接
技巧 6:妙用 session 对象
技巧 7:在 com 对象中封装代码
技巧 8:晚点获取资源,早点释放资源
技巧 9:进程外的执行将牺牲可靠性
技巧 10:显式使用选项
技巧 11:在子例程和函数中使用局部变量
技巧 12:将常用数据复制到脚本变量
技巧 13:避免重新定义数组
技巧 14:使用响应缓冲
技巧 15:批处理内嵌脚本和 response.write 语句
技巧 16:在开始长时间的任务之前先使用 response.isclientconnected
技巧 17:使用 <object> 标记实例化对象
技巧 18:使用 ado 对象和其他组件的 typelib 绑定
技巧 19:利用浏览器的验证能力
技巧 20:在循环中避免字符串串联
技巧 21:启用浏览器和代理缓存
技巧 22:尽可能使用 server.transfer 替代 response.redirect
技巧 23:在目录 url 尾部加斜线
技巧 24:避免使用服务器变量
--------------------------------------------------------------------------------
简介
性能是一个特性。您需要预先设计性能,或是在日后重新编写应用程序。换句话说,什么是最大限度优化 active server pages (asp) 应用程序性能的好策略?
本文为优化 asp 应用程序和"visual basic(r) 脚本编辑器 (vbscript)"提供了许多技巧。对许多陷阱和缺陷进行了讨论。本文所列的建议均在 http://www.microsoft.com 及其他站点上进行了测试,而且工作正常。本文假定您对 asp 开发有基本的理解,包括对 vbscript 和/或 jscript、asp application、asp session 和其他 asp 内部对象(请求、响应和服务器)。
asp 的性能,通常不止取决于 asp 代码本身。我们并不想在一篇文章中囊括所有的至理名言,只在最后列出与性能相关的资源。这些链接包括 asp 和非 asp 主题,包括"activex(r) 数据对象 (ado)"、"部件对象模型 (com)"、数据库和"internet 信息服务器 (iis)"配置。这些是我们喜欢的链接 - 务请关注它们。
技巧 1:在 web 服务器上缓存常用数据
典型的 asp 页从后端数据库检索数据,然后将结果转换为超文本标记语言 (html)。无论数据库的速度如何,从内存检索数据要比从后端数据库检索数据快得多。从本地硬盘读取数据通常也要比从数据库检索数据快得多。因此,通常可以通过在 web 服务器(在内存或磁盘)上缓存数据来改善性能。
缓存是典型的空间与时间的折衷。如果恰当地缓存数据,您将看到性能会有惊人的提高。为使缓存发挥效力,它必须保持经常重用的数据,而且重新计算这些数据的代价是昂贵的或比较昂贵的。如果缓存充满了垃圾数据,则是对存储器的浪费。
不经常变化的数据也是缓存的候选数据,因为您无须担心数据与数据库的同步问题。组合框、引用表、dhtml 碎片、可扩展标记语言 (xml) 字符串、菜单项和站点配置变量(包括数据源名称 (dsn)、internet 协议 (ip) 地址和 web 路径)都是缓存的候选数据。注意,您可以缓存数据的表示而不是数据本身。如果 asp 页不经常更改,而且缓存的成本也非常高(例如,整个产品目录),请考虑预先生成 html,而不是在每次请求时重新绘制。
数据应缓存在何处,有哪些缓存策略?数据经常缓存在 web 服务器内存或 web 服务器磁盘上。下面两个技巧讨论这些选项。
技巧 2:在 application 或 session 对象中缓存常用数据
asp application 和 session 对象为在内存中缓存数据提供了方便的容器。既可以将数据赋予 application 对象,也可将数据赋予 session 对象,这些数据在 http 调用中将保留在内存中。session 数据按用户存储,而 application 数据在所有用户间共享。
何时将数据载入 application 或 session?通常,在 application 或 session 启动时加载数据。要在 application 或 session 启动时加载数据,请在下面两函数中添加相应的代码:
application_onstart()
或
session_onstart()
。这两个函数应该位于 global.asa;如果没有,可以添加这些函数。也可以在第一次需要数据时加载数据。要进行上述操作,请在 asp 页中添加一些代码(或编写可重用的脚本函数),这些代码检查数据是否存在,并在数据不存在时加载数据。这是称为迟缓计算的经典性能技术的例子 - 在您的确需要它之前,不进行计算。请看例子:
<%
function getemploymentstatuslist
dim d
d = application("employmentstatuslist")
if d = "" then
' fetchemploymentstatuslist 函数(不显示)
' 从 db 中取出数据,返回数组
d = fetchemploymentstatuslist()
application("employmentstatuslist") = d
end if
getemploymentstatuslist = d
end function
%>
可以为每一块所需的数据编写类似的函数。
数据应该以什么格式存储?任何变量类型均可存储,因为所有脚本变量是各不相同的。例如,可以存储字符串、整型或数组。通常,您将以这些变量类型之一存储 ado 记录集的内容。若要获取 ado 记录集衍生的数据,可以手工将数据复制到 vbscript 变量中,每次一个字段。使用一个 ado 记录集保留函数 getrows()、getstring() 或 save() (ado 2.5),会更快更简便。完整而详细的内容已超出了本文的范围。下面的演示函数使用了
getrows()
来返回记录集数据的数组:
' 取记录集,以数组返回
function fetchemploymentstatuslist
dim rs
set rs = createobject("adodb.recordset")
rs.open "select statusname, statusid from employeestatus", _
"dsn=employees;uid=sa;pwd=;"
fetchemploymentstatuslist = rs.getrows() ' 以数组返回数据
rs.close
set rs = nothing
end function
对上面示例的进一步改进应当是缓存该列表的 html,而不是缓存数组。下面是一个简单的范例:
' 取记录集,以"html 选项"列表返回
function fetchemploymentstatuslist
dim rs, fldname, s
set rs = createobject("adodb.recordset")
rs.open "select statusname, statusid from employeestatus", _
"dsn=employees;uid=sa;pwd=;"
s = "<select name=""employmentstatus">" & vbcrlf
set fldname = rs.fields("statusname") ' ado 字段绑定
do until rs.eof
' 下面一行违背了不要进行字符串连接,
' 但这是可以的,因为我们正在建立高速缓存
s = s & " <option>" & fldname & "</option>" & vbcrlf
rs.movenext
loop
s = s & "</select>" & vbcrlf
rs.close
set rs = nothing ' 参见尽早释放
fetchemploymentstatuslist = s ' 以字符串返回数据
end function
在正常的情况下,可以在 application 或 session 作用域中缓存 ado 记录集本身。有两个警告:
ado 必须为标记的*线程
必须使用断开连接的记录集。
如果不能保证满足这两个要求,请不要缓存 ado 记录集。在下面的非灵活组件和不要缓存连接技巧中,我们将讨论在 application 或 session 作用域中存储 com 对象的危险。
如果在 application 或 session 作用域中存储数据,这些数据将一直保留在那儿,直到在程序中改变它、session 过期或 web 应用程序重新启动时为止。数据需要更新如何处理?若要用手工强制更新应用程序数据,可以调用只允许管理员访问的数据更新 asp 页。另外,还可以通过函数,周期地自动刷新数据。下面的示例存储带缓存数据的时间戳,在指定时间间隔后刷新数据。
<%
' 未显示错误处理...
const update_interval = 300 ' 刷新时间间隔,以秒计
' 函数返回雇佣状态列表
function getemploymentstatuslist
updateemploymentstatus
getemploymentstatuslist = application("employmentstatuslist")
end function
' 定期更新缓存的数据
sub updateemploymentstatuslist
dim d, strlastupdate
strlastupdate = application("lastupdate")
if (strlastupdate = "") or _
(update_interval datediff("s", strlastupdate, now)) then
' 注意:此处可能有两个或多个调用。这是可以的,只不过
' 产生几个不必要的取指令罢了(就此有一个工作区)
' fetchemploymentstatuslist 函数(不显示)
' 从 db 中取数据,返回一个数组
d = fetchemploymentstatuslist()
' 更新 application 对象。用 application.lock()
' 来确保一致的数据
application.lock
application("employmentstatuslist") = d
application("lastupdate") = cstr(now)
application.unlock
end if
end sub
其他示例,请参阅具有 application 数据的最快列表框(英文)。
请注意,在 session 或 application 对象中缓存大型数组并非上策。在访问数组元素之前,脚本语言的语法要求建立整个数组的临时副本。例如,如果在 application 对象中缓存了将美国邮政编码映射到本地气象站的字符串数组,该字符串数组有 100,000 个元素,asp 在找出一个字符串之前,必须将所有 100,000 个气象站复制到临时数组中。在这种情况下,建立带自定义方法的自定义组件,来存储气象站 - 或使用一个字典组件,也许更好。
请不要在倒洗澡水时把孩子一同倒掉,对这种观点的一个新的注解是:数组提供了对内存中相邻关键-数据对的快速查找和存储。索引字典比索引数组要慢。您应该根据具体情况选择能够提供最佳性能的数据结构。
技巧 3:在 web 服务器磁盘上缓存数据和 html
有时,数据过多不能在内存中进行缓存。"过多"是一种定性的判断;它取决于打算消耗的内存量,还有缓存项的数量和这些项的检索频率。总之,如果有过多的数据要在内存中缓存,请考虑以文本或 xml 文件的形式,在 web 服务器的硬盘上缓存数据。可以将在磁盘上缓存数据和在内存中缓存数据组合起来,为站点建立最优的缓存策略。
注意,在度量单个 asp 页的性能时,在磁盘上检索数据不一定比从数据库中检索数据快。但是,缓存减轻了数据库和网络的负荷。在高负荷情况下,这将明显提高总体通信量。在查询成本很高时缓存查询的结果,缓存便非常有效,例如多表联合或复杂的存储过程,或缓存大型的结果集。按照惯例,测试竞争方案。
asp 和 com 提供了几种构建磁盘缓存方案的工具。ado 记录集的 save() 和 open() 函数,保存和加载磁盘上的记录集。您可以使用这些方法重写上面 application 数据缓存技巧中的范例代码,用 save() 文件替换向 application 对象写入数据的代码。
还有其他一些处理文件的组件:
scripting.filesystemobject 使您能够创建、读取和写入文件。
msxml 是随 internet explorer 提供的 microsoft(r) xml 解析器,它支持保存和加载 xml 文档。
lookuptable 对象(在 msn 上使用的范例)是从磁盘加载简单列表的良好选择。
最后,请考虑在磁盘上缓存数据的表示,而不是数据本身。预制的 html 可以作为 .htm 或 .asp 文件存储在磁盘上;超级链接可以直接指向这些文件。可以使用商业工具,如 xbuilder 或 microsoft(r) sql server 的 internet 发行功能来自动化 html 生成过程。另外,可以将 html 片段 #include 到 .asp 文件。还可以使用 filesystemobject 从磁盘读取 html 文件或使用 xml 进行早期调整(英文)。
技巧 4:避免在 application 或 session 对象中缓存非灵活组件
虽然在 application 或 session 对象中缓存数据是个好主意,但是缓存 com 对象可能有严重缺陷。将常用 com 对象嵌入 application 或 session 对象通常具有吸引力。遗憾的是,很多 com 对象,包括用 visual basic 6.0 或更早版本编写的 com 对象,在 application 或 session 对象中存储时将导致严重的瓶颈。
特别是任何非灵活组件,在 session 或 application 对象中缓存时将导致性能瓶颈。灵活组件是标记为
threadingmodel=both
的组件(它聚集了*线程汇集器 (ftm))或标记为
threadingmodel=neutral
的组件(windows(r) 2000 和 com+ 中新增的"中性"模型。)下列组件是非灵活的:
*线程组件(除非它们聚集了 ftm)。
单元线程组件。
单线程组件。
已配置组件(microsoft transaction server (mts)/com+ 库和服务器包/应用程序)为非灵活组件,除非它们是"中性"线程的。单元线程组件和其他非灵活组件最适于在页作用域工作(也就是说,它们在单个 asp 页上创建和销毁)。
在 iis 4.0 中,标记为
threadingmodel=both
的组件被视为灵活的。在 iis 5.0 中,这已经不够了。组件不仅必须标记为 both,而且还必须聚集 ftm。灵活性文章说明了如何使得用"活动模板库"编写的 c++ 组件聚集 ftm。请注意,如果组件缓存接口指针,这些指针本身必须为灵活的、或者必须存储在"com 全局接口表 (git)"中。如果不能重新编译 both 线程组件,使它聚集 ftm,则可以将该组件标记为
threadingmodel=neutral
。另外,如果不希望 iis 进行灵活性检查(这样,希望非灵活组件能够存储在 application 或 session 作用域中),可以在 metabase 中设置
asptrackthreadingmodel
为
true
。不主张更改
asptrackthreadingmodel
。
如果试图在 application 对象中存储用
server.createobject
创建的非灵活组件,iis 5.0 将产生错误。可以通过在 global.asa 中使用
<object runat=server scope=application ...>
解决该问题,但是不主张这样做,因为这将导致汇集和串行化,说明如下。
如果缓存非灵活组件,会发生什么错误呢?缓存在 session 对象中的非灵活组件,将把会话"锁定"到某个 asp 工作器线程。asp 维护着一个工作器线程池,它向请求提供服务。通常,新的请求由第一个可用的工作器线程来处理。如果 session 被锁定到某个线程,则该请求将不得不等待它所关联的线程变为可用。打个比方:您进入一个超市,挑选了一些食品,然后在第 3 号收款台交款。从这以后,每当您在这个超市购买食品,都不得不始终在第 3 号收款台交款,即使是在其他收款台人少或没人时。
将非灵活组件存储在 applicaton 作用域甚至会对性能产生更严重的影响。asp 将不得不创建专用的线程来运行非灵活的、applicaton 作用域内的组件。这将导致两种后果:所有调用不得不被汇集到该线程,而且所有调用被串行化。汇集意味着:参数不得不存储在内存的共享区;对该专用线程执行昂贵的上下文切换;组件的方法被执行;结果汇集到共享区域;以及经过另一个昂贵的上下文切换,使控制权返回原来的线程。串行化意味着所有方法必须一个挨一个地运行(同一时刻只能运行一个方法)。两个不同的 asp 工作器线程不可能同时执行共享组件上的方法。这将扼杀并行机制,尤其是在多处理器计算机上。更坏的是,所有非灵活的、application 作用域内的组件都将共享一个线程("host sta"),所以串行化的影响更加严重。
是否感到困惑?下面我们提出几个通用规则。如果您正在用 visual basic (6.0) 或更早版本编写对象,请不要将它们缓存在 application 或 session 对象中。如果您不知道对象的线程模型,就不要缓存它。不要缓存非灵活对象,而应当在每页上创建并释放它们。对象将直接运行在 asp 工作器线程上,这样,将不会发生汇集或串行化。如果 com 对象正运行在 iis 框中,而且如果它们没有花很长时间来初始化和取消,性能将是足够的。注意,不要用该方法使用单线程对象。小心:vb 可以创建单线程的对象!如果您必须以该方式使用单线程的对象(如 microsoft excel 电子表格),则不要期望有很高的吞吐量。
当 ado 被标记为*线程时,则缓存 ado 记录集是安全的。要将 ado 标记为*线程,请使用 makfre15.bat 文件,该文件通常位于如下目录中:\\program files\common\system\ado。
警告: 如果您正在用 microsoft access 作为数据库,则不应当将 ado 标记为*线程。通常,ado 记录集还必须是断开连接的,如果您不能控制站点的 ado 配置(例如,您是独立的软件厂商 [isv],将 web 应用程序卖给客户,然后由他们来管理他们自己的配置),那么不缓存记录集可能会更好。
词典组件也是灵活对象。lookuptable 从数据文件加载它的数据,并且它对组合框数据和配置信息是有用的。来自 duwamish books 的 pagecache 对象提供了目录语义,和 caprock dictionary 的表现一样。这些对象或它们的派生对象可以构成有效缓存策略的基础。注意,scripting.dictionary 对象不是灵活的,所以不应当存储在 application 或 session 作用域。
技巧 5:不要在 application 或 session 对象中缓存数据库连接
缓存 ado 连接通常是不好的策略。如果一个 connection 对象存储在 application 中,并在所有页上使用,那么所有页将竞争使用该连接。如果 connection 对象存储在 asp session 对象中,那么将为每个用户创建数据库连接。这将连接池的好处毁于一旦,并对 web 服务器和数据库产生不必要的压力。
取代缓存数据库连接的方法是,在每个使用 ado 的 asp 页上创建并取消 ado 对象。这是个有效的方法,因为 iis 具有内置的数据库连接池。更准确的说,iis 自动启用 oledb 和 odbc 连接池。这确保了创建并取消每个页上的连接将是有效的。
由于被连接的记录集中存储有对数据库连接的引用,所以,不应当在 application 或 session 对象中缓存被连接的记录集。但是,可以安全地缓存断开连接的记录集,因为它不包含对其数据连接的引用。要断开记录集的连接,请执行如下两个步骤:
set rs = server.createobject("adodb.recordset")
rs.cursorlocation = aduseclient ' 第 1 步
' 植入带数据的记录集
rs.open strquery, strprov
' 现在断开记录集同数据提供者和数据源的连接
rs.activeconnection = nothing ' 第 2 步
有关连接池的详细信息,请参阅 ado 和 sql server(英文)引用。
技巧 6:妙用 session 对象
在肯定了在 applications 和 sessions 中缓存的优点之后,我们建议您避免使用 session 对象。下面将会谈到,当用于忙碌站点时,sessions 有几个缺点。所谓忙碌,通常是指站点每秒请求数百页或同时有数千个用户。该技巧对于必须进行水平扩展的站点,即那些利用多个服务器来适应负载或执行容错功能的站点来说,更加重要。对于较小的站点,如 intranet 站点,sessions 的便利,与开销相比也是值得的。
为了翻新,asp 自动为每个访问 web 服务器的用户创建一个 session。每个 session 有大约 10 kb 内存开销(在存储在 session 中的任何数据中是最高的),并使所有的请求都慢了一点。session 一直保持活动状态,直到达到可配置的超时(通常 20 分钟)为止。
session 最大的问题不是性能而是可伸缩性。session 不能跨越 web 服务器;一旦在一个服务器上创建了 session,它的数据就保持在那里。这意味着,如果您在 web 领域中使用 sessions,您将不得不为每个用户的请求设计一种策略,以便始终将这些请求引向用户的 session 所在的服务器。这被称为将用户"粘"到 web 服务器上。术语"粘性会话"即来源于此。由于 session 没有保持到磁盘上,所以,当 web 服务器崩溃时,被"粘住"的用户将丢失他们的 sessions 状态。
用于实施粘性会话的策略包括硬件和软件解决方案。如 windows 2000 advanced server 中的网络负载平衡解决方案和 cisco 公司的"本地指向器"解决方案可以实施粘性会话,但以牺牲一些可伸缩性为代价。这些解决方案并不完美。我们不主张您现在全盘推翻您的软件解决方案(我们过去常用 isapi 筛选器和 url 矫直对方案进行检查)。
application 对象也不能跨越服务器;如果您需要在 web 领域内共享并更新 application 数据,则需要使用后端数据库。但只读的 application 数据在 web 领域中仍然有用。
如果只是为了增加正常运行时间(用于处理故障转移和服务器维护),大多数执行重要任务的站点将需要部署至少两台 web 服务器。所以,在设计执行重要任务的应用程序时,您将需要实施"粘性会话",或者简单地避开 sessions 以及其他任何在单个 web 服务器上存储用户状态的状态管理技术。
如果当前没有使用 sessions,请确保将它们关闭。可以通过"internet 服务管理器"(请参阅 ism 文档)来为应用程序执行该操作。如果决定使用 sessions,可以采取几个方法来将对性能的影响降低到最小。
可以将不需要 sessions 的内容(如"帮助"屏幕、访问者区域等)移动到关闭了 sessions 的、单独的 asp 应用程序中。可以逐页提示 asp:在给定的页中您不需要 session 对象;使用位于 asp 页顶端的如下指令:
<% @enablesessionstate=false %>
使用该指令的一个很好的原因是,session 给框架集带来了有趣的问题。asp 保证任何时候只执行一个来自 session 的请求。这样可以确保如果浏览器为一个用户请求了多个页时,在每一时刻只有一个 asp 请求将进入 session;这就避免了在访问 session 对象时出现多线程问题。遗憾的是,结果,框架集中的所有页均被以串行化方式绘制,一个接一个地,而不是同时地。这样,用户可能不得不等待很长时间才能得到所有框架内容。这意味着:如果某些框架页不信任 session,一定要使用
@enablesessionstate=false
指令告诉 asp。
作为使用 session 对象的替代方式,有很多方法可以用来管理 session 状态。对于状态数量较小的情况(不到 4 kb),通常建议使用 cookies、querystring 变量和隐藏形式的变量。对于较大数量的数据,如购物推车,则使用后端数据库是最合适的选择。关于在 web 服务器领域中的状态管理技术已经有很多资料。详细信息,请参阅 会话状态(英文)。
技巧 7:在 com 对象中封装代码
如果您有很多 vbscript 或 jscript,那么您可以通过把代码移动到已编译的 com 对象来经常改进它们的性能。已编译的代码通常比被解释代码运行得更快。已编译的 com 对象可以通过"早期绑定"访问其他 com 对象,这种调用 com 对象方法的手段,比脚本所使用的"后期绑定"更有效。
将代码封装在 com 对象种有如下好处(超越性能):
com 对象是将表达逻辑与业务逻辑分隔开来的好办法。
com 对象启用了代码重用。
很多开发商发现,用 vb、c++ 或 visual j++ 书写的代码,比 asp 更容易调试。
com 对象有一些缺点,包括初始开发时间以及需要不同的编程技巧。需要警告您的是,封装"少"量的 asp 可能会导致性能降低,而不是提高。通常,在少量 asp 代码封装到 com 对象时出现这样的情况。这时候,创建和调用 com 对象的开销,超过了已编译代码的好处。至于 asp 脚本和 com 对象代码怎样合并才能产生最佳性能还有待测试。注意,与 windows nt(r) 4.0/iis 4.0 相比,microsoft 已经在 windows 2000/iis 5.0 中极大地提高了脚本和 ado 性能。这样,已编译代码对 asp 代码的性能优势已经随着 iis 5.0 的引入而降低。
有关在 asp 中使用 com 对象的优缺点的更多讨论,请参阅 asp 组件准则和用 com 和 microsoft visual basic 6.0 对分布式应用程序进行编程(英文)。如果您的确部署了 com 组件,要对它们进行强度测试是非常重要的。实际上,所有 asp 应用程序都应当作为正式过程进行强度测试。
技巧 8:晚点获取资源,早点释放资源
这是个小技巧。通常,最好晚点获取资源而要早点释放资源。这些资源包括 com 对象、文件句柄和其他资源。
ado 连接和记录集是这种优化的首要目标。当您使用完记录集,就是说用它的数据打印完一个表格后,请立即将它释放,而不是等到页的末尾。将您的 vbscript 变量设置为
nothing
是最好的做法。不要让记录集简单地脱离作用域。同时,应当释放任何有关的 command 或 connection 对象。(不要忘了对记录集或"连接"调用
close()
,在将它们设置为
= nothing
之前。)这将缩短数据库必须为您调整资源的时间跨度,并将数据库连接尽可能快地释放给连接池。
1
上一篇: cls_main.asp第1/3页
下一篇: asp截取字符串的两种应用 原创