二十八条改善ASP性能和外观的技巧(二)

在适当的条件下,可以将ADO记录集本身缓存在Application或Session作用域中。有两个警告:必须将ADO标记为自由线程必须使用断开连接的记录集。如果不能保证满足这两个要求,则不要缓存ADO记录集。在下面的“非敏捷组件”和“不要缓存连接”技巧中,我们将讨论将COM对象存储在Application或Session作用域中的危险性。当您将数据存储在Application或Session作用域时,数据将保留在那里,直到您以编程方式改变它、Session过期或Web应用程序重新启动为止。如果数据需要更新怎么办?要手工强制对Application数据进行更新,您可以访问只有管理员才可访问的ASP页来更新数据。或者,您可以通过函数定期自动刷新数据。下面例子存储带有缓存数据的时间戳,并隔一段时间后刷新数据。’FunctiontoreturntheemploymentstatuslistFunctionGetEmploymentStatusListUpdateEmploymentStatusGetEmploymentStatusList=Application(?EmploymentStatusList?)EndFunction’PeriodicallyupdatethecacheddataSubUpdateEmploymentStatusListDimd,strLastUpdatestrLastUpdate=Application(?LastUpdate?)If(strLastUpdate=??)Or_(UPDATE_INTERVAL<DateDiff(?s?,strLastUpdate,Now))Then’Note:twoormorecallsmightgetinhere.Thisisokayandwillsimply’resultinafewunnecessaryfetches(thereisaworkaroundforthis)’FetchEmploymentStatusListfunction(notshown)’fetchesdatafromDB,returnsanArrayd=FetchEmploymentStatusList()’UpdatetheApplicationobject.UseApplication.Lock()’toensureconsistentdataApplication.LockApplication(?EmploymentStatusList?)=EventsApplication(?LastUpdate?)=CStr(Now)Application.UnlockEndIfEndSub请参见World’sFastestListBoxwithApplicationData,上面还有一个例子。要知道在Session或Application对象中缓存大的数组不是一个好的做法。在访问数组的任何元素之前,脚本语言的语法要求必须临时复制整个数组。例如,如果将由字符串组成的有100,000个元素的数组(该数组将美国邮政编码映射到当地的气象站)缓存在Application对象中,ASP必须先将所有的100,000个气象站复制到临时数组中,然后才能提取一个字符串。在这种情况下,用自定义方法建立一个自定义组件来存储气象站-或使用一个词典组件会更好。再警告大家一下,不要将婴儿与洗澡水一起倒掉:数组能快速查寻和存储在内存中是邻近的关键数据对。索引一个词典比索引一个数组要慢得多。应针对您的实际情况,选择提供最佳性能的数据结构。 技巧3:将数据和HTML缓存在Web服务器的磁盘上有时,数据可能太多,无法都缓存在内存中。“太多”只是一个说法,这要看您想消耗多少内存,以及需缓存的项目数和检索这些项目的频率。在任何情况下,如果数据太多而无法都缓存在内存中,则考虑将数据以文本或XML文件缓存在Web服务器的硬盘上。可以同时将数据缓存在磁盘和内存中,为您的站点建立最适宜的缓存策略。注意当测量单个ASP页的性能时,检索磁盘上的数据可能不一定要比从数据库检索数据更快。但缓存会降低数据库和网络上的负载。在高负载的情况下,这样做可大大改善总体吞吐量。当缓存开销很大的查询结果(如多表联接或复合存储过程)或大的结果集时,这是非常有效的。与往常一样,要测试一下几种方案的优劣。ASP和COM提供一些建立基于磁盘的缓存方案的工具。ADO记录集 Save()Open()函数保存和装载磁盘中的记录集。可以使用这些方法重新编写上面Application数据缓存技巧中的代码示例,用文件的Save()代替写到Application对象中的代码。有一些其它组件可以用于文件:
  1. Scripting.FileSystemObject可使您创建、读和写文件。
  2. 与InternetExplorer一起提供的Microsoft®XML解析器(MSXML)支持保存和装载XML文档。
  3. LookupTable对象(例如,用在MSN上)是从磁盘装载简单列表的最好选择。
最后,应考虑将数据的表示缓存在磁盘上,而不是数据本身。预先转换的HTML可以用.htm或.asp文件存储在磁盘上,超级链接可以直接指向这些文件。可以使用商用工具,如XBuilder,或Microsoft®SQLServer™Internet发布功能将产生HTML的过程自动化。或者,您可以将HTML代码片断放在.asp文件中。还可以使用FileSystemObject从磁盘读取HTML文件,或使用XML尽早转换。 技巧4:避免将非敏捷的组件缓存在Application或Session对象中尽管将数据缓存在Application或Session对象中是一个好的做法,但缓存COM对象却有严重的陷阱。通常,人们倾向于将经常使用的COM对象缓存到Application或Session对象中。很遗憾,许多COM对象(包括所有以VisualBasic6.0或更低版本编写的对象)当存储在Application或Session对象时,会引起严重的瓶颈。具体来讲,当任何不敏捷的组件缓存在Session或Application对象时,将引起性能瓶颈。敏捷的组件是被标记为ThreadingModel=Both的组件,它聚集Free-threadedmarshaler(FTM);或被标记为ThreadingModel=Neutral的组件。(Neutral模型是Windows®2000和COM+的新增模型。)下列组件不是敏捷的:
  1. 自由线程的组件(除非它们聚集FTM)。
  2. 单元线程组件。
  3. 单线程组件。
配置的组件(MicrosoftTransactionServer(MTS)/COM+库和服务器程序包/应用程序)不是敏捷的,除非它们是Neutral线程。单元线程组件和其它非敏捷的组件在页作用域内是最适合的(即,它们在单个ASP页上创建和销毁)。在IIS4.0中,被标记为ThreadingModel=Both的组件被认为是敏捷的。在IIS5.0中,只有这一点还不够。组件必须不仅被标记Both,还必须聚集FTM。有关敏捷性的文章讲述了如何使以ActiveTemplateLibrary编写的C++组件聚集FTM。要注意如果组件缓存界面指针,那么那些指针本身必须是敏捷的,或必须存储在COM共用界面表(GIT)中。如果您不能重新编译Both线程组件以聚集FTM,那么您可以将组件标记为ThreadingModel=Neutral。或者,如果您不想让IIS执行敏捷性检查(因此,您可以允许非敏捷的组件存储在Application或Session作用域中),您可以在配置数据库中将AspTrackThreadingModel设置为True。不建议更改AspTrackThreadingModel。如果您想将以Server.CreateObject创建的非敏捷的组件存储在Application对象中,IIS5.0将出现一个错误。您可以在Global.asa中使用<objectrunat=serverscope=application...>避免这一错误,但不建议这样做,因为这会导致汇集和串行化,关于这一点将在下面讲述。如果您缓存非敏捷的组件会出现什么毛病?缓存在Session对象中的非敏捷的组件将Session锁定于ASP工作者线程。ASP维护一个工作者线程池来处理请求。通常情况下,一个新请求总是由第一个可用的工作者线程来处理。如果Session被锁定于一个线程,那么请求必须等到其相关的线程可用为止。这里有一个类比,也许会有所帮助:您去一家超级市场,挑选了一些商品,并在#_3收款台付款。其后,每当您在那家超级市场为商品付款时,您总是必须在#_3收款台付款,即使其它收款台前排队的人较少或者没有人排队,也是如此。将非敏捷的组件存储在Application作用域对性能的影响甚至更坏。ASP必须创建一个特殊的线程运行存储在Application作用域中的非敏捷组件。这会有两个结果:所有调用都必须汇集到此线程,且所有调用都排成长队。“汇集”的意思是参数必须存储在内存的共享区域;执行一个开销很大的到特殊线程的上下文切换;执行组件的方法;将结果汇集到共享区域;执行另一个开销很大的上下文切换,将控制返回到原始的线程。“串行化”意思是指每次只运行一个方法。两个不同的ASP工作者线程不能同时在共享组件上执行多个方法。这样就杜绝了并发性,特别是在多处理器计算机上。更糟的是,所有非敏捷的Application作用域的组件共享一个线程(主机STA),因此串行化的影响甚至更显著。如之奈何?下面是一些一般的规则。如果您使用VisualBasic(6.0)或更早版本编写对象,那么不要将它们缓存在Application或Session对象中。如果您不知道对象的线程模型,不要缓存它。不要缓存非敏捷的对象,而应在每个页面创建和释放它们。对象直接在ASP工作者线程上运行,因此没有汇集或串行化。如果COM对象在IIS服务器上运行,且如果它们不花长时间初始化和删除,性能尚可。注意单线程对象不应该这样使用。小心-VB可创建单线程对象!如果您必须这样使用单线程对象(如MicrosoftExcel电子表格),别指望会有很高的吞吐量。当ADO被标记为自由线程,ADO记录集可以安全地缓存。要将ADO标记为自由线程,使用Makfre15.bat文件,该文件通常位于目录//ProgramFiles/Common/System/ADO中。 警告如果您使用MicrosoftAccess作为数据库,不应将ADO标记为自由线程的。ADO记录集也必须切断连接。一般来说,如果您不能控制站点中的ADO配置(例如,您是一个独立的软件厂商[ISV],向管理他们自己的配置客户销售Web应用程序),最好不要缓存记录集。词典组件也是敏捷的对象。LookupTable从数据文件中装载其数据,可用于组合框数据和配置信息。DuwamishBooks中的PageCache对象可提供词典语法,CaprockDictionary也可提供。这些对象或其派生对象可以构成有效缓存策略的基础。注意Scripting.Dictionary对象不是敏捷的,不应该存储在Application或Session作用域中。 技巧5:不要将数据库连接缓存在Application或Session对象中缓存ADO连接通常是很糟糕的策略。如果一个Connection对象存储在Application对象中,并在所有的页面中使用,那么所有页面将争抢这一连接。如果Connection对象存储在ASPSession对象中,那么将为每个用户创建数据库连接。这就会使连接池的优势荡然无存,并给Web服务器和数据库带来不必要的压力。可以不缓存数据库连接,而是在使用ADO的每个ASP页面中创建和删除ADO对象。这是很有效的,因为IIS内嵌了数据库连接池。更准确地说,IIS自动启用OLEDB和ODBC连接池。这就能确保在每个页面上创建和删除连接将是有效的。因为连接的记录集存储一个到数据库连接的引用,所以您不应将连接的记录集缓存在Application或Session对象中。但是,您可以安全地缓存断开连接的记录集,它们不保存到其数据连接的引用。要断开记录集连接,执行下面的两个步骤:Setrs=Server.CreateObject(?ADODB.RecordSet?)rs.CursorLocation=adUseClient’step1’Populatetherecordsetwithdatars.OpenstrQuery,strProv’Nowdisconnecttherecordsetfromthedataprovideranddatasourcers.ActiveConnection=Nothing’step2 技巧6:合理地使用Session对象既然我们已经讨论了缓存在Application和Session中的优点,现在开始讨论避免使用Session对象的问题。正如下面所讨论的,当与忙的站点一起使用时,Session有几个缺点。“忙”的意思一般是指一秒钟要求几百页面或成千上万同时用户的站点。这个技巧对于必须水平扩展的站点-即,那些利用多台服务器以处理负载或实现容错的站点-甚至更重要。对于较小的站点,诸如Intranet站点,要想实现Session带来的方,必然增大系统开销。简言之,ASP自动为每个访问Web服务器的用户创建一个Session。每个Session大约需要10KB的内存开销(最主要的是数据存储在Session中),这就使所有的请求都减慢。在配置的超时时段(通常是20分钟)结束以前,Session一直保留有效。Session的最大的问题不是性能,而是可扩展性。Session不能跨越几台Web服务器,一旦在一台服务器上创建Session,其数据就留在那儿。这就意味着如果您在一个Web服务器群使用Session,您必须设计一个策略,将每个用户请求始终发到用户Session所在的那台服务器上。这被称为将用户“粘”在Web服务器上。术语“粘性会话”就是从这里派生而来的。如果Web服务器崩溃,被“粘住的”用户将丢失他们的会话状态,因为会话不是粘到磁盘上。实现粘性会话的策略包括硬件和软件解决方案。诸如Windows2000AdvancedServer中的网络负载平衡和Cisco的LocalDirector之类的解决方案都可以实现粘性会话,代价是要损失一定程度的可扩展性。这些解决方案是不完善的。不建议此时部署您自己的软件解决方案(我们过去常常使用ISAPI筛选器和URL转换等等)。Application对象也不跨越多台服务器,如果您必须跨越Web服务器群共享和更新Application数据,您必须使用后端数据库。但是,只读Application数据在Web服务器群中仍是有用的。如果只是因为要增加运行时间(处理故障转移和服务器维护),大多数关键任务站点至少需部署两台Web服务器。因此,在设计关键任务应用程序时,必须实现“粘性会话”,或干脆避免使用Session,以及任何其它将用户状态存储在单个Web服务器上的状态管理技术。如果您不使用Session,一定要将它们关闭。您可以通过InternetServicesManager,为应用程序执行此操作(参见ISM文档)。如果您决定使用Session,您可以采用一些方法减轻它们对性能的影响。您可以将不需要Session的内容(如帮助屏幕,访问者区域等等)移到另一个关闭了Session的ASP应用程序中。您可以逐页提示ASP,您不再需要该页面上的Session对象,使用以下放在ASP页面最上面的指令:<%@EnableSessionState=False%>使用这一指令有一个很好的理由是,这些Session在框架集方面存在一个有意思的问题。ASP保证任何时候Session只有一个请求执行。这样就确保如果浏览器为一个用户请求多个页面,一次只有一个ASP请求接触Session,这样就避免了当访问Session对象时发生的多线程问题。很遗憾,一个框架集中的所有页面将以串行方式显示,一个接一个,而不是同时显示。用户可能必须等候很长时间,才能看到所有的框架。该故事的寓意:如果某些框架集页面不依靠Session,一定要使用@EnableSessionState=False指令告诉ASP。有许多管理Session状态的方法,可替代Session对象的使用。对于少量的状态(少于4KB),我们通常建议使用Cookies、QueryString变量和隐式变量。对于更大数据量,如购物小车,后端数据库是最适合的选择。有关Web服务器群中状态管理技术的文章很多。有关详细信息,请参见Session状态参考资料。 技巧7:将代码封装在COM对象中如果您有许多VBScript或JScript,您可以经常将代码移到编译的COM对象中,从而可改善性能。编译的代码通常比解释的代码运行得更快。编译的COM对象可以通过“早绑定”访问其它COM对象,与脚本使用的“晚绑定”相比,“早绑定”是调用COM对象的更有效方法。将代码封装在COM对象中还有一些优点(除性能之外):
  1. COM对象有利于将表示逻辑与业务逻辑分开。
  2. COM对象可以保证代码重复使用。
  3. 许多开发人员发现以VB、C++或VisualJ++编写的代码比ASP更容易调试。
COM对象也有缺点,包括初始开发时间和需要不同的程序设计技巧。注意封装少量的ASP可能引起性能下降,而不会得到性能改进。这种情况通常在少量的ASP代码被封装进COM对象时发生。在这种情况下,创建和调用COM对象的系统开销超过了编译的代码的优点。应反复地试验,以确定什么样的ASP脚本和COM对象代码的组合产生最好的性能。注意,与MicrosoftWindowsNT®4.0/IIS4.0相比,Windows2000/IIS5.0中在脚本和ADO性能方面有了很大的改进。因此,随着IIS5.0的推出,编译代码比ASP代码的性能优势有所降低。有关在ASP中使用COM的优点和缺点的详细讨论,参见ASPComponentGuidelinesandProgrammingDistributedApplicationswithandMicrosoftVisualBasic6.0。如果您部署COM组件,以负荷对它们进行测试特别重要。事实上,理所当然应对所有的ASP应用程序进行负荷测试。 技巧8:迟一点获得资源,早一点释放资源这里是一个小技巧供您参考。一般来说,最好迟一点获得资源,早一点释放资源。这适用于COM对象以及文件句柄和其它资源。这种优化方法主要用于ADO连接和记录集。当您使用完记录集,比方说在显示一个表及其数据之后,应立即释放它,而不是等到页面结束时再释放。将VBScript变量设置为Nothing是最好的做法。不要让记录集超出作用域之外。而且,要释放任何相关的Command或Connection对象(在将记录集或连接设置为=Nothing之前,不要忘记调用Close())。这会缩短数据库必须为您准备资源的时间,并尽快释放数据库到连接池的连接。 技巧9:进程外执行过程以性能换取可靠性ASP和MTS/COM+两者都有配置选项,可使您兼顾可靠性和性能。当建立和部署应用程序时,应知道如何兼顾两者的性能。 ASP选项可以配置ASP应用程序,以便以三种方法之一运行。在IIS5.0中,引入了“隔离级”这一术语以说明这些选项。这三个隔离级分别是低级、中级和高级:
  1. 低级隔离。这在IIS的所有版本中都得到支持,且是最快的。它在Inetinfo.exe中运行ASP,Inetinfo.exe是主要IIS进程。如果ASP应用程序崩溃,IIS也会崩溃。(要在IIS4.0下重新启动IIS,Web站点管理员应使用诸如InetMon之类的工具监视站点,如果服务器发生故障,应启用批处理文件以重新启动服务器。IIS5.0引入了可靠的重新启动,该方法可使发生故障的服务器自动重新启动。)
  2. 中级隔离。IIS5.0引入了这个新的级别,它被称为进程外级别,因为ASP在IIS进程之外运行。在中级隔离中,被配置作为中级隔离运行的所有ASP应用程序都共享一个进程空间。这就减少了在一台服务器运行多个进程外ASP应用程序所需要的进程数量。中级隔离是IIS5.0中的默认隔离级别。
  3. 高级隔离。在IIS4.0和IIS5.0中支持这一级别,高级隔离也是进程外的。如果ASP崩溃,Web服务器并不会崩溃。下次ASP请求时,ASP应用程序就会自动重新启动。在高级隔离中,配置作为高级隔离运行的每个ASP应用程序都在其自有进程空间中运行。这样做可保护ASP应用程序彼此之间不相互干扰。其缺点是它要求每个ASP应用程序都要有一个单独的进程。当在一台服务器上必须运行许多应用程序时,系统开销就会大大增加。
哪个选项最好的呢?在IIS4.0中,进程外运行将显著降低性能。在IIS5.0中,做了许多改进,将进程外运行ASP应用程序所产生的开销降到最低限度。事实上,在绝大多数测试中,IIS5.0中的ASP进程外应用程序比IIS4.0中的进程内应用程序运行得更快。不管怎样,在两个平台上,进程内(低隔离级)性能最佳。但是,如果访问率相对较低或最大吞吐量较低,低隔离级的优势不太明显。因此,在您每一Web服务器每秒钟需要数百或成千上万页面时,才会觉得有必要设置低隔离级。与往常一样,应对多种配置进行测试,确定您要采取哪一种折衷方案。 注意当您运行ASP进程外应用程序时(中级或高级隔离),它们在NT4中的MTS和在Windows2000中的COM+中运行。即,在NT4中它们在Mtx.exe中运行;而在Windows2000中,它们在DllHost.exe中运行。您可以在任务管理器中看到这些进程在运行。您还可以看到IIS如何为进程外ASP应用程序配置MTS程序包或COM+应用程序。 COM选项COM组件也有三种配置选项,虽然与ASP选项不完全类似。COM组件可以是“未配置的”、配置为库应用程序或配置为服务器应用程序。“未配置的”意思是指组件没有注册COM+。组件将在调用程序的进程空间运行,那就是说,它们是“进程内的”。库应用程序也是进程内的,但使用COM+的服务,包括安全、事务和上下文支持。服务器应用程序被配置为在它们自有的进程空间内运行。您可以看到未配置的组件比库应用程序略有一些优势。库应用程序比服务器应用程序的性能优点更大。这是因为库应用程序与ASP在同一进程内运行,而服务器应用程序在它们的自有进程内运行。进程间的调用比进程内调用开销更大。而且,当在进程之间传递诸如记录集之类的数据时,必须在两个进程之间复制所有的数据。陷阱!当使用COM服务器应用程序时,如果您在ASP和COM之间传递对象,要确保对象执行“按值汇集”或MBV。执行MBV的对象将它们自己从一个进程复制到另一个进程。这比下面一种方法好,采用这种方法时,对象仍在创建者的进程中,另外一个进程反复地调用创建进程以使用该对象。切断连接的ADO记录集将“按值汇集”,连接的记录集则不然。Scripting.Dictionary不执行MBV,且不在进程之间传递。最后,VB程序员请注意:MBV不通过传递参数ByVal获得。MBV由原始的组件作者执行。 怎么办?如果让我们建议一个兼顾性能与可靠性的合理配置,它们应是如下的配置:
  1. 在IIS4.0中,使用ASP低隔离级别,使用MTS服务器程序包。
  2. 在IIS5.0上,使用ASP的中隔离级,并使用COM+库应用程序。技巧10:使用显式选项在.asp文件中应使用OptionExplicit。此指令放在.asp文件的最上面,它强制开发人员声明要使用到的所有变量。许多程序员认为这种方法对于调试应用程序很有帮助,因为这种方法避免了键错变量名和误建新变量的可能性(例如,将MyXMLString=)错写成MyXLMString=...。更重要的一点也许是,声明的变量比未声明的变量速度更快。由此,脚本在运行时每次用到未声明的变量时,按名称引用它。另一方面,声明的变量是有顺序的,要么以编译时间,要么以运行时间。以后,声明的变量都按此顺序引用。因为OptionExplicit强制变量声明,它能确保声明所有变量,因此访问的速度也很快。技巧11:在子例程和函数中使用局部变量局部变量是那些在子例程和函数内声明的变量。在函数或子例程内,局部变量访问比全局变量访问更快。局部变量的使用也会使代码更清晰,因此应尽量使用局部变量。技巧12:将经常使用的数据复制到脚本变量中当访问ASP中的COM对象时,应将经常使用的对象数据复制到脚本变量中。这样做可减少COM方法调用,因为COM方法调用与访问脚本变量相比,开销相对较大。当访问Collection和Dictionary对象时,这种技术也会减少开销很大的查找。一般来说,如果您打算不止一次访问对象数据,那么就应将数据放到脚本变量中。这种优化的主要目标是Request变量(Form和QueryString变量)。例如,您的站点可传递一个名为UserID的QueryString变量。假设此UserID在特定页面上被引用12次。可以无须调用Request(?UserID?)12次,而是在ASP页面最上面将UserID指派到一个变量。然后在该页面自始至终使用该变量。这样就省去了11次COM方法调用。实际上,访问COM属性或方法的开销并没有那么大。下面举一个例子,说明某相当常见的代码(从语法上讲):Foo.bar.blah.baz=Foo.bar.blah.qaz(1)IfFoo.bar.blah.zaq=Foo.bar.blah.abcThen’...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值