如何增强asp程序性能(1)
2000-08-10· 甘冀平·yesky
简介
性能是一个很重要的特征。你需要事先设计好性能指标,否则日后就要为此重新编写程序。就是说:要设想好怎样最佳化地执行asp程序?
本文提出了一些优化asp应用和vbscript的技巧,许多技巧和缺陷都经过了研讨。这里列出的建议已经在http://www.microsoft.com 和其他站点上进行了测试,都工作得非常好。本文假设你具备asp开发的基本知识,包括vbscript或者jscript,asp应用程序,asp session,以及其他asp内置对象(request,response和server)。
通常,asp的执行性能远远不仅仅依赖asp代码本身!在本文的尾部列出了与性能相关的资源,它们含概了asp和非asp的部分,包含activex data objects(ado),component object model(com),数据库(database),以及internet信息服务器(iis)的配置。除了这些,还有一些非常好的链接值得你一看。
技巧1:在web服务器上缓存经常使用的数据
典型的情况是:asp页面从后台存储中取回数据,然后以超文本标记语言(html)的形式形成结果。不管数据库的速度如何,从内存中取回数据要比从后台存储设备中快得多。从本地硬盘读取数据通常也非常快。所以,提高性能可以通过缓存服务器上的数据来实现,无论是将数据缓存在内存中,或者本地硬盘中。
缓存是经典的“空间换时间”的折中方式。如果缓存得恰当,就可以看到显著的性能提升。为了让缓存有效,必须保证缓存数据是经常要重用的,而且也是计算起来繁琐的。装满陈旧数据的缓存是对内存的浪费。
不经常改变的数据是缓存的较好对象,因为不需要随时考虑这些数据更新后的同步操作。组合框、参考表格、dhtml代码、扩展标记语言串、菜单以及站点配置变量(包括数据源名字dsns,internet协议地址ip以及web路径)都是很好的缓存对象。注意:要缓存数据表达式而不是数据本身。如果一个asp页面经常变化并且很费力去缓存(比如整个产品目录),就要考虑预产生html,而不是每次发生请求时再描述它。
技巧2:在application或session对象中缓存经常使用的数据
asp中的application和session对象是在内存中缓存数据的便利容器。你可以将数据赋值给application和session对象,这些数据在http调用期间将一直保持在内存中。session中的数据是为每一个用户服务的,application中的数据是所有用户共享的。
何时需要在application和session中装入数据?通常,当应用程序启动或者会话开始时,数据就被装入了。为了在这时装入数据,在application onstart()或者session onstart()中分别添加适当的代码。这些函数位于文件global.asa中,如果原来不存在,就添加上。也可以在数据首次需要的时候调入,在asp页面中添加代码,检查数据是否存在,如果没有发现,就调入它。这里有一个例子,它代表了被称为“lazy evalution”的经典性能处理技术:直到需要时,再去计算。例子如下:
<%
function getemploymentstatuslist
dim d
d = application("employmentstatuslist")
if d = "" then
fetchemploymentstatuslist function (not shown)
fetches data from db, returns an array
d = fetchemploymentstatuslist()
application("employmentstatuslist") = d
end if
getemploymentstatuslist = d
end function
%>
对于不同的数据,可以编写类似的函数代码。
数据应该按什么格式保存?任何变量类型都可以,因为所有的脚本变量都是不同的。比如说,可以保存为字符串、整型或者数据。通常,将ado记录集的内容存储到这些变量类型中一个。为了从ado记录集中取出数据,需要手工地拷贝数据到vbscript变量中,每次一个字段。使用任意一个ado记录集的函数functions getrows(),getstring() 或者 save() (ado 2.5)都非常得快速而且简单,这里有个函数,描述了如何使用getrows()返回记录集数据的数组:
get recordset, return as an array
function fetchemploymentstatuslist
dim rs
set rs = createobject("adodb.recordset")
rs.open "select statusname, statusid from employeestatus", _
"dsn=employees;uid=sa;pwd=;"
fetchemploymentstatuslist = rs.getrows() " return data as an array
rs.close
set rs = nothing
end function
上述代码的一个更深的技巧是为列表缓存了html。下面是个简单的例子:
get recordset, return as html option list
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 field binding
do until rs.eof
next line violates dont do string concats,
but its ok because we are building a cache
s = s & " <option>" & fldname & "</option>" & vbcrlf
rs.movenext
loop
s = s & "</select>" & vbcrlf
rs.close
set rs = nothing see release early
fetchemploymentstatuslist = s return data as a string
end function
在合适的环境下,可以在application或者session中缓存ado记录集本身,但是有2点提示:
ado必须是自由线程标记的
需要使用disconnected recordset方式
如果不能保证上述2个条件,就不要缓存ado记录集,因为这会产生很大的危险性。
当在application或session中保存数据后,数据将一直保持,除非程序改变它、session变量到期或者web应用程序重新启动。如果数据需要更新,怎么办?可以调用只有管理员才能访问的asp页面来更新数据,或者,通过函数周期性的自动更新数据。下面的例子中,与缓存数据一起保存了时钟标记,过一段时间后,就刷新数据。
<%
error handing not shown…
const update_interval = 300 refresh interval, in seconds
function to return the employment status list
function getemploymentstatuslist
updateemploymentstatus
getemploymentstatuslist = application("employmentstatuslist")
end function
periodically update the cached data
sub updateemploymentstatuslist
dim d, strlastupdate
strlastupdate = application("lastupdate")
if (strlastupdate = "") or _
(update_interval < datediff("s", strlastupdate, now)) then
note: two or more calls might get in here. this is okay and will simply
result in a few unnecessary fetches (there is a workaround for this)
fetchemploymentstatuslist function (not shown)
fetches data from db, returns an array
d = fetchemploymentstatuslist()
update the application object. use application.lock()
to ensure consistent data
application.lock
application("employmentstatuslist") = d
application("lastupdate") = cstr(now)
application.unlock
end if
end sub
有另外一个例子,请参阅 world’s fastest listbox with application data。
必须意识到,在session或者application对象中缓存大容量的数组不是一个好的方法。存取数组中任何元素前,脚本语言的规则要求首先要建立整个数组的临时备份。比如,如果在application对象中缓存一个100,000个元素的数组,其中包含u.s.邮政编码与本地气象站的对应关系,asp就必须首先拷贝所有100,000个气象站信息到临时数组中,然后才能选择其中一个字符串进行处理。在这种情况下,创建一个定制的组件,编写一个方法存储气象站信息,是非常好的方法。
技巧3:在web服务器磁盘上缓存数据和html页面
有时候,有“许多”数据要在内存中缓存。“许多”是相对而言的,它取决于能消耗多少内存、缓存项目的数量以及取回数据的频度。任何情况下,如果需要在内存中缓存大量的数据,请考虑以text或者xml文件格式在web服务器硬盘上做缓存。当然,也可以混合使用硬盘缓存数据以及内存缓存数据,从而达到最佳缓存。
注意:当测试一个单一asp页面的性能时,从磁盘取回数据不一定比从网络数据库中取回数据快,但是缓存减少了网络数据库的调用。在大规模调用时,这将明显地提高网络的吞吐能力。缓存一个费时的查询结果是非常有用的,比如对于一个复杂的存储过程,或者大量的结果数据。
asp和com提供了几种建立基于磁盘缓冲配置的工具。ado记录集的save()和 open()函数负责保存和调入磁盘上的记录集。另外还有一些组件:
scripting.filesystemobject 允许你创建、读取和写文件
msxml,microsoft xml 解析器随internet explorer而来,支持保存和装入xml文档
lookuptable对象(比如在msn上使用)是从磁盘调入简单列表的很好选择。
最后,考虑缓存磁盘数据的表达式,而不是数据本身。预处理的html可以存储为.htm或者.asp文件,链接直接指向它们。使用诸如xbuilder或者microsoft sql server internet发布类的商业工具,能够自动处理这些过程。而且,也可以在.asp文件中包含html程序片断。同样,也可使用filesystemobject从磁盘上读取html文件,或者使用xml for early rendering来做这个工作。
技巧4:避免在application或session对象中缓存非轻快型组件
在application或session对象中缓存数据是一个很好的方法,但是,缓存com对象却有严重的缺陷。在application或session对象中缓存经常使用的com对象这个工作是非常吸引人的,但是很不幸,许多com对象,包括用visual basic 6.0或者以前版本编写的对象组件,当存储在application或session对象中后,都会产生严重的瓶颈问题。
特别地,当组件编写得不是很轻巧时,就极可能产生瓶颈问题。一个轻型组件就是标记了threadingmodel=both的组件,其中合计了自由线程的排列(ftm),或者标记了threadingmodel=neutral(neutral模式是windows2000和com+中的新特征)。下面的组件不是轻型的:
free-threaded组件(除非被集合成ftm)
apartment-threaded组件
single-threaded组件
configured components不是轻型组件,除非它们是neutral-threaded的。apartment-threaded组件和其他非轻型组件在页范围内工作得很好,就是说,它们是在一个单一asp页面中创建并释放的。
如果缓存了非轻型组件,将会发生什么错误?在session对象中缓存的非轻型组件将会“锁住”会话。asp维护着一个响应请求的工作线程池,通常,新的请求被第一个可用的工作线程控制。如果一个会话被锁在一个线程中,那么请求就被迫等待到相关的线程变为可用。这里有一个类比:你前往一个超级市场,挑选了一些食品,并在3号付款台付款。从那以后,只要在那个超级市场买食品付款,你就会经常到3号付款台去,虽然其他的付款台人少些甚至没有人。
技巧5:不要在application或session对象中缓存数据库连接
缓存ado连接通常不是一个好的策略。如果一个连接对象被存储在application对象中,并在所有的页面使用,那么所有页面将会争夺该连接的使用。如果存储在asp session对象中,那么将要为每一个用户都打开数据库连接。这将挫败连接池的使用意图,并且在web服务器和数据库上都施加了不必要的高代价压力。
为了替代缓存数据库连接,可以在使用ado的每个asp页面中创建并释放ado对象。这将非常有效,因为iis拥有内建的数据库连接池。更准确地说,iis自动处理oledb和odbc连接池,这将保证在每个页面创建并且释放连接的工作高效进行。
由于连接的记录集存储了数据库连接的参考,所以,不要在application或session对象中缓存连接的记录集。然而,可以安全地缓存disconnected类型的记录集,它们并不保存相应数据库连接的参考。为了断开记录集,执行下面2步:
set rs = server.createobject("adodb.recordset")
rs.cursorlocation = aduseclient step 1
populate the recordset with data
rs.open strquery, strprov
now disconnect the recordset from the data provider and data source
rs.activeconnection = nothing step 2
更多的关于连接池的信息请访问 ado and sql server。
