[p]本文介绍了优化 asp 应用程序和 visual basic® scripting edition (vbscript) 的技巧。本文讨论了许多陷阱。本文列出的建议已经在 [url=http://microsoft.com/]http://microsoft.com[/url] 和其它站点中进行了测试,效果十分显著。本文假定您已经对 asp 开发,包括 vbscript 和/或 jscript、asp application、asp session 和其它 asp 固有对象(request、response 和 server)有了基本了解。[/p]
[p]通常,asp 性能主要取决于 asp 代码本身以外的很多因素。我们不在一篇文章中罗列出所有的信息,在本文结尾处我们列出了与性能有关的资源。这些链接涵盖了 asp 和非 asp 主题,包括 activex® 数据对象 (ado)、组件对象模型 (com)、数据库和 internet information server (iis) 配置。这些都是我们喜欢的一些链接 - 一定要去看看。[/p]
[p][b]技巧 1:将经常使用的数据缓存在 web 服务器上[/b][/p]
[p] 典型的 asp 页从后端数据存储中检索数据,然后将结果转换成超文本标记语言 (html)。无论数据库的速度如何,从内存中检索数据总要比从后端数据存储中检索数据快得多。从本地硬盘读取数据通常也比从数据库中检索数据更快。因此,通常可以将数据缓存在 web 服务器上(存储在内存或磁盘中),来提高性能。[/p]
[p] 缓存是传统的以空间换取时间的做法。如果您缓存的内容正确,那么您可以看到性能会有显著的提高。为使缓存有效,必须保存那些经常重复使用的数据,且要重新计算这些数据需要(适度)大的开销。如果缓存的都是些陈旧的数据,就会造成内存浪费。[/p]
[p] 不经常发生改变的数据是很好的缓存候选数据,因为您不必担心随着时间的迁移该数据与数据库同步的问题。组合框列表、引用表、dhtml 碎片、扩展标记语言 (xml) 字符串、菜单项和站点配置变量(包括数据源名称 (dsn)、internet 协议 (ip) 地址和 web 路径)都是很好的缓存候选内容。注意您可以缓存数据的“表示”,而不缓存数据本身。如果 asp 页很少更改,且缓存的开销也很大(例如,整个产品目录),则应考虑事先产生 html,而不是在响应每个请求时重新显示。[/p]
[p] 应将数据缓存在哪里,有哪些缓存策略?通常,数据缓存在 web 服务器的内存或磁盘中。下两个技巧讲述了这两个方法。[/p]
[p][b]技巧 2: 将经常使用的数据缓存在 application 或 session 对象中[/b][/p]
[p] asp application 和 session 对象为将数据缓存在内存中提供了方便的容器。您可以将数据指派到 application 和 session 对象中,这些数据在 http 调用之间保留在内存中。session 数据是按每个用户分别存储的,而 application 数据则在所有用户之间共享。[/p]
[p] 什么时候将数据装载到 application 或 session 中呢?通常,数据是在启动 application 或 session 时装载。要在 application 或 session 启动过程中装载数据,应将适当的代码分别添加到 application_onstart() 或 session_onstart() 中。这些函数应在 global.asa 中,如果没有,则可以添加这些函数。还可以在第一次需要时装载该数据。为此,在 asp 页中添加一些代码(或编写一个可重复使用的脚本函数),以检查数据是否存在,如果不存在,就装载数据。这是一个传统的性能技术,称为“惰性计算” - 在您知道需要某一个值以前不计算该值。例如:[/p]
[p]<% [br]function getemploymentstatuslist [br]dim d [br]d = application(?employmentstatuslist?) [br]if d = ?? then [br]' fetchemploymentstatuslist function (not shown) [br]' fetches data from db, returns an array [br]d = fetchemploymentstatuslist() [br]application(?employmentstatuslist?) = d [br]end if [br]getemploymentstatuslist = d [br]end function [br]%>[/p]
[p] 可以为所需要的每个数据块编写类似的函数。[/p]
[p] 应以什么格式存储数据?可以存储任何变体类型,因为所有脚本变量都是变体型。例如,您可以存储字符串、整数或数组。通常,您将以这些变量类型之一存储 ado 记录集的内容。要从 ado 记录集获取数据,您可以手工将数据复制到 vbscript 变量,一次一个字段。使用一个 ado 记录集持久函数 getrows()、getstring() 或 save()(ado 2.5),可加快速度且更容易一些。其详细情况已超出本文所讨论的范围,但下面给出了一个函数举例,说明使用 getrows() 返回记录集数据的一个数组:[/p]
[p]' get recordset, return as an array [br]function fetchemploymentstatuslist [br]dim rs [br]set rs = createobject(?adodb.recordset?) [br]rs.open ?select statusname, statusid from employeestatus?, _ [br]?dsn=employees;uid=sa;pwd=;? [br]fetchemploymentstatuslist = rs.getrows() ? return data as an array [br]rs.close [br]set rs = nothing [br]end function[/p]
[p][br] 对上面举例做更进一步改进,可以将 html 缓存为列表,而不是数组。下面是简单的示例:[/p]
[p]' get recordset, return as html option list [br]function fetchemploymentstatuslist [br]dim rs, fldname, s [br]set rs = createobject(?adodb.recordset?) [br]rs.open ?select statusname, statusid from employeestatus?, _ [br]?dsn=employees;uid=sa;pwd=;? [br]s = ?
? & vbcrlf [br]rs.close [br]set rs = nothing ' see release early [br]fetchemploymentstatuslist = s ' return data as a string [br]end function[/p]
[p] 在适当的条件下,可以将 ado 记录集本身缓存在 application 或 session 作用域中。有两个警告:[/p]
[p] 必须将 ado 标记为自由线程 [br] 必须使用断开连接的记录集。 [br] 如果不能保证满足这两个要求,则不要缓存 ado 记录集。在下面的“非敏捷组件”和“不要缓存连接”技巧中,我们将讨论将 com 对象存储在 application 或 session 作用域中的危险性。[/p]
[p] 当您将数据存储在 application 或 session 作用域时,数据将保留在那里,直到您以编程方式改变它、session 过期或 web 应用程序重新启动为止。如果数据需要更新怎么办?要手工强制对 application 数据进行更新,您可以访问只有管理员才可访问的 asp 页来更新数据。或者,您可以通过函数定期自动刷新数据。下面例子存储带有缓存数据的时间戳,并隔一段时间后刷新数据。[/p]
[p]<% [br]' error handing not shown... [br]const update_interval = 300 ' refresh interval, in seconds [br][br]' function to return the employment status list [br]function getemploymentstatuslist [br]updateemploymentstatus [br]getemploymentstatuslist = application(?employmentstatuslist?) [br]end function [br][br]' periodically update the cached data [br]sub updateemploymentstatuslist [br]dim d, strlastupdate [br]strlastupdate = application(?lastupdate?) [br]if (strlastupdate = ??) or _ [br](update_interval < datediff(?s?, strlastupdate, now)) then [br][br]' note: two or more calls might get in here. this is okay and will simply [br]' result in a few unnecessary fetches (there is a workaround for this) [br][br]' fetchemploymentstatuslist function (not shown) [br]' fetches data from db, returns an array [br]d = fetchemploymentstatuslist() [br][br]' update the application object. use application.lock() [br]' to ensure consistent data [br]application.lock [br]application(?employmentstatuslist?) = events [br]application(?lastupdate?) = cstr(now) [br]application.unlock [br]end if [br]end sub[/p]
[p] 请参见 world's fastest listbox with application data,上面还有一个例子。[/p]
[p] 要知道在 session 或 application 对象中缓存大的数组不是一个好的做法。在访问数组的任何元素之前,脚本语言的语法要求必须临时复制整个数组。例如,如果将由字符串组成的有 100,000 个元素的数组(该数组将美国邮政编码映射到当地的气象站)缓存在 application 对象中,asp 必须先将所有的 100,000 个气象站复制到临时数组中,然后才能提取一个字符串。在这种情况下,用自定义方法建立一个自定义组件来存储气象站 - 或使用一个词典组件会更好。[/p]
[p] 再警告大家一下,不要将婴儿与洗澡水一起倒掉:数组能快速查寻和存储在内存中是邻近的关键数据对。索引一个词典比索引一个数组要慢得多。应针对您的实际情况,选择提供最佳性能的数据结构。[/p]
[p]#p#[/p]
[p][b]技巧 3:将数据和 html 缓存在 web 服务器的磁盘上[/b][/p]
[p] 有时,数据可能太多,无法都缓存在内存中。“太多”只是一个说法,这要看您想消耗多少内存,以及需缓存的项目数和检索这些项目的频率。在任何情况下,如果数据太多而无法都缓存在内存中,则考虑将数据以文本或 xml 文件缓存在 web 服务器的硬盘上。可以同时将数据缓存在磁盘和内存中,为您的站点建立最适宜的缓存策略。[/p]
[p] 注意当测量单个 asp 页的性能时,检索磁盘上的数据可能不一定要比从数据库检索数据更快。但缓存会降低数据库和网络上的负载。在高负载的情况下,这样做可大大改善总体吞吐量。当缓存开销很大的查询结果(如多表联接或复合存储过程)或大的结果集时,这是非常有效的。与往常一样,要测试一下几种方案的优劣。[/p]
[p] asp 和 com 提供一些建立基于磁盘的缓存方案的工具。ado 记录集 save() 和 open() 函数保存和装载磁盘中的记录集。可以使用这些方法重新编写上面 application 数据缓存技巧中的代码示例,用文件的 save() 代替写到 application 对象中的代码。[/p]
[p] 有一些其它组件可以用于文件:[/p]
[p] scripting.filesystemobject 可使您创建、读和写文件。 [br] 与 internet explorer 一起提供的 microsoft® xml 解析器 (msxml) 支持保存和装载 xml 文档。 [br] lookuptable 对象(例如,用在 msn 上)是从磁盘装载简单列表的最好选择。 [br] 最后,应考虑将数据的表示缓存在磁盘上,而不是数据本身。预先转换的 html 可以用 .htm 或 .asp 文件存储在磁盘上,超级链接可以直接指向这些文件。可以使用商用工具,如 xbuilder,或 microsoft® sql server™ internet 发布功能将产生 html 的过程自动化。或者,您可以将 html 代码片断放在 .asp 文件中。还可以使用 filesystemobject 从磁盘读取 html 文件,或使用 xml 尽早转换。[/p]
[p][b]技巧 4:避免将非敏捷的组件缓存在 application 或 session 对象中[/b][/p]
[p] 尽管将数据缓存在 application 或 session 对象中是一个好的做法,但缓存 com 对象却有严重的陷阱。通常,人们倾向于将经常使用的 com 对象缓存到 application 或 session 对象中。很遗憾,许多 com 对象(包括所有以 visual basic 6.0 或更低版本编写的对象)当存储在 application 或 session 对象时,会引起严重的瓶颈。[/p]
[p] 具体来讲,当任何不敏捷的组件缓存在 session 或 application 对象时,将引起性能瓶颈。敏捷的组件是被标记为 threadingmodel=both 的组件,它聚集 free-threaded marshaler (ftm);或被标记为 threadingmodel=neutral 的组件。(neutral 模型是 windows® 2000 和 com+ 的新增模型。) 下列组件不是敏捷的:[/p]
[p] 自由线程的组件(除非它们聚集 ftm)。 [br] 单元线程组件。 [br] 单线程组件。 [br] 配置的组件(microsoft transaction server (mts)/com+ 库和服务器程序包/应用程序)不是敏捷的,除非它们是 neutral 线程。单元线程组件和其它非敏捷的组件在页作用域内是最适合的(即,它们在单个 asp 页上创建和销毁)。[/p]
[p] 在 iis 4.0 中,被标记为 threadingmodel=both 的组件被认为是敏捷的。在 iis 5.0 中,只有这一点还不够。组件必须不仅被标记 both,还必须聚集 ftm。有关敏捷性的文章讲述了如何使以 active template library 编写的 c++ 组件聚集 ftm。要注意如果组件缓存界面指针,那么那些指针本身必须是敏捷的,或必须存储在 com 共用界面表 (git) 中。如果您不能重新编译 both 线程组件以聚集 ftm,那么您可以将组件标记为 threadingmodel=neutral。或者,如果您不想让 iis 执行敏捷性检查(因此,您可以允许非敏捷的组件存储在 application 或 session 作用域中),您可以在配置数据库中将 asptrackthreadingmodel 设置为 true。不建议更改 asptrackthreadingmodel。[/p]
[p] 如果您想将以 server.createobject 创建的非敏捷的组件存储在 application 对象中,iis 5.0 将出现一个错误。您可以在 global.asa 中使用