从 MTS 移植到 COM+ 时保持应用程序的性能

 原文地址: http://msdn.microsoft.com/zh-cn/library/ms809941.aspx

 

从 MTS 移植到 COM+ 时保持应用程序的性能(COM+ 技术文章)
发布日期 : 5/11/2004 | 更新日期 : 5/28/2004

Michael McKeown

解决方案集成工程组

2002 年 3 月

适用于:

Microsoft Windows NT

Microsoft Windows 2000

Microsoft Transaction Server

Microsoft COM+

本页内容

简介 简介
安装最新的 COM+ Hotfix Rollup Service Pack 安装最新的 COM+ Hotfix Rollup Service Pack
避免从单线程单元 (STA) 长时间运行方法调用 避免从单线程单元 (STA) 长时间运行方法调用
注意跨上下文的封送处理 注意跨上下文的封送处理
仅使用基本的 COM+ 服务 仅使用基本的 COM+ 服务
遵照每客户端一对象模型 遵照每客户端一对象模型
可能的话使用“Both”线程模型 可能的话使用“Both”线程模型
辅助功能注意事项 辅助功能注意事项
参考资料 参考资料

摘要:本文讨论在将运行于 Windows NT Server 下的 Microsoft Transaction Server (MTS) 组件移植到 COM+ 和 Windows 2000 时,如何保持应用程序的性能。(10 页打印页)

简介

使一些人非常沮丧的是,在将 n 层应用程序从 Microsoft Windows NT Server/MTS 移植到 Microsoft Windows 2000/COM+ 时,他们会面临实际上的性能下降。仅仅使用默认的设置并且不对其代码做任何更改,他们的应用程序的响应性可能会出乎意料地大大降低。有人不禁会问:“我没有做任何更改呀,可我的应用程序却变得很蹩脚了,我现在究竟该怎么办呢?”

别气馁。要知道,无论如何性能隔离都不是简单的任务。它往往是一个由经验、测试、数据搜集、研究、反复试验、调查侦测、奉献以及一点点巫术组成的令人费解的混合物(当然并不需要按以上的顺序!)。在现今 n 层应用程序的极其错综复杂的领域里,许多因素会影响到响应性。而且,当解决了一个问题时,可能会暴露系统体系结构中的另一个弱点,甚至也许是与 COM+ 无关的弱点!

在本文中,我突出说明我们解决方案集成工程 (SIE) 组在协助大型公司完成从 MTS 到 COM+ 的移植过程时发现的主要问题。我的目的是为您提供一个初始方向,可以由此开始进行研究并解决您的性能问题。本文决不意味着对每一个问题进行深入的技术讨论,也并不包含您可能遇到的所有问题。我尽量简明扼要地解释问题背后的“为什么”。但是,为使本文档保持一定的简洁,我们假定您并不是初次开发 “Hello World” 式的 n 层应用程序,并假定您对于 COM 线程处理、MTS 以及 COM+ 有着扎实的理解。

有关详细信息,请参阅 参考资料

安装最新的 COM+ Hotfix Rollup Service Pack

为提高性能并将难以发现的 COM+ 错误造成的影响减至最小,记得始终安装最新版本的 COM+ hotfix rollup。它包含累积的 COM+ 错误修补以及大量的性能增强。而且它是免费、快捷、易于安装的。可以通过您的 PSS(产品支持服务)联系人处从 Microsoft 获取最新的修补程序。截至本文,最新的版本是 rollup #19。在未安装最新的 COM+ 版本时企图隔离错误是不明智的决策。

避免从单线程单元 (STA) 长时间运行方法调用

如果在本文中只能讲述一个主题,这将是我唯一要写的主题。毫无例外地,当从 MTS 移植到 COM+ 时,从“单元”组件长时间运行的方法调用是唯一一个最普遍遇到的性能问题。

MTS STA 线程池将一个活动绑定到一个线程,并增加到每个进程 100 个线程。在那一点上,MTS 将使传入的方法调用排队等候直到有线程能够处理请求。

在 COM+ 下,托管的 STA 线程池由 7+n 个线程开始,并能增长到 10*n 个线程(这里“n”是 CPU 的数量)。例如,在一个单 CPU 系统中,STA 池从开始的 8 个增至最多 10 个 STA 线程。与 MTS 不同,COM+ 最多可将 5 个活动绑定到一个线程。由于它将多个活动绑定到一个线程,如果第一个要获得服务的活动进行了 OOA(单元外)调用,它所使用的时间可以是所有挂起活动的总和。让我们来看一个简短示例以了解这一过程是如何进行的。

假设有 5 个活动绑定到一个池化的 COM+ STA 线程。活动 1 获取了线程并且开始执行它的方法。在该方法调用的中途,它进行了一个 OOA 调用。COM 通道加入线程的消息循环,并在当 RPC 发送线程进行实际阻塞调用时继续发送消息。当活动 1 等待方法调用返回时,现在该活动 2 获取服务了。这里的问题在于,活动 1 在活动 2 彻底完成了工作之前,无法重获对 STA 线程的控制。如果活动 2 也进行了 OOA 调用,活动 3 获得了 STA 线程,这一过程能一直持续到活动 5(假设所有这些活动都进行了 OOA 调用)。

活动 1 必须等待活动 2 完成,而活动 2 的完成必须等待活动 3 完成,活动 3 则必须等待 4 完成,如此类推。最终结果是,如果这些调用长时间运行,由于 COM+ 对每个线程至多排队 5 个活动,第一个活动的方法是它自己所用执行时间再与其余 4 个活动的时间之和!

在 MTS 之下,在线程池被耗尽之前,长时间的运行调用是不会导致什么问题的。在此之前,没有哪个线程会阻止其他调用者获得服务,它们自己的完成也不会受到阻碍。但是如果有 100 个线程,对于正确使用短持续时间的方法调用写成的应用程序,上下文的切换将会成为对性能的真正拖累。COM+ STA 线程池对短时间运行的方法调用进行了优化,使用较少的线程来最大限度地减少上下文的切换。因此,应该尽量避免长时间运行的方法调用。与客户端-服务器计算不同,典型的 n 层处理就好像去看牙医 £-“进入和离开”应尽可能地快。由于 MTS STA 线程池的大小以及绑定到 MTS STA 线程池的活动,您能够掩盖体系结构中的缺陷。在 COM+ 之下,它可能被暴露。

有关详细信息,请参阅 Thread Pool Differences Between COM+ and MTS (Q282490)

建议

  • 由于 COM+ 线程池算法是基于 CPU 的数量,可以在系统中添加另外一个 CPU。

  • 您可以重新设计方法,将它们分解成更小的处理操作以使它们不会长时间地运行以及形成本质上的独占情况。

  • 回顾 INFO:Registry Key for Tuning COM+ Thread and Activity (Q303071) ?Dμ? ”ActivitiesPerThread“。从每个线程两个活动开始,然后三个,以此类推,直到获得了期望的吞吐量为止。

  • 您可以把 COM+ STA 线程池配置成好像 MTS 线程池那样(使用 100 个线程并且每线程一个活动)运行。这可以通过一个叫做 EmulateMTSBehavior 的注册表项来实现。在设计 COM+ 线程池机制以提高性能时,这将是您最后的选择,使用该算法确有合理的原因。有关详细信息,请参阅 INFO:Registry Key for Tuning COM+ Thread and Activity (Q303071)

注意跨上下文的封送处理

COM+ 上下文主要是指为使一个组件在某个环境(事务、同步,等等)中顺利运行所需的已配置好的运行时属性。任何使用基于侦听的 COM+ 服务的对象都有它自己的上下文。

COM+ 与 MTS 之间在方法执行上的差异是,在 MTS 下,一个对象最核心的执行实体是单元。现在,这一实体在 COM+ 下成为了上下文。在同一个单元内但是在不同的上下文中组件间的调用,需要用一个轻量的代理(仅封送接口指针)封送处理。由于在 MTS 中不存在上下文,因而仅在跨单元时发生最低级别的封送处理。这取决于被封送的对象是什么,也许会成为一个显著的影响性能的因素。

在较早的 COM+ 版本中,对 ADO(活动数据对象)记录集的封送处理是通过值跨上下文传递。 如果有一个有 50,000 条记录的记录集,克隆并传递该记录集而不是传递接口指针。这一问题在随后的COM+ rollup 版本中解决。有关对该问题的叙述,请参阅 FIX:ADO Recordset Loses Filter Property When Marshaled In-Proc (Q264442)

然而,如果对很多的接口指针进行封送处理,额外的系统开销依然是个问题。

建议

如果可能的话,尽量避免在方法调用中传递接口指针。通过遵照 object-per-client模型,避免跨上下文的性能影响,并消除围绕分布式对象系统的主要争用来源。不传递接口指针的跨上下文调用不封送任何东西,几乎类似于一个直接调用。

如果必须传递接口指针且又发现产生了性能影响,则请浏览使用 COM+ 配置设置 “Must Activate In Caller's Context“。有关详细信息,请参阅 HOWTO:Activate a COM+ Component in Its Caller's Context (Q261096)

仅使用基本的 COM+ 服务

在 MTS 下不存在的某些 COM+ 服务会微妙地影响您应用程序的性能。因此,应该仅使用那些对于您的应用程序最优化运行所必需的服务

本节中讨论这些主题:

额外侦听的开销

在每一个针对配置的 COM+ 组件的方法调用中,代理和 stub 解释策略设置。这些设置公布所需的 COM+ 服务,因而在组件实例化(比如在一个事务中登记,等等)之前环境必须存在。对于每一个方法调用,必须运行系统侦听代码以确保组件在正确的环境中执行。这一过程要花费时间。除非您知道需要一个服务来正确工作,否则不要只因为它是默认设置而使用它。

建议

理解 COM+ 配置设置值在应用程序上的效果并确定您是否真的需要一个基于侦听的服务。关于 COM+ 设置的详细信息,请参见在参考资料一节中列出的前两个参考。

实时激活

实时 (JIT) 激活默认情况下是打开的。当要对未池化组件调用 IObjectControl::Activate 和 IObjectControl::Deactivate 方法时,需要 JIT。如果要使用同步或者事务,需要 JIT。如果要在代码中调用 IObjectContext::SetAbort 或者 IObjectContext::SetComplete,同样需要 JIT

不要仅因为 JIT 是默认值就使用它。跨方法调用停用一个组件有助于共享资源管理的理论是错误的。如果您通过 Activate 或 Deactivate 来获得和释放共享资源,而组件中只有一些方法使用该共享资源,则该过程是无关紧要的。更有效的方法是,将资源管理代码移动至需要该资源的各个方法的起始和结束处,跨方法调用时不要将资源存储在全局存储区,并且只在需要时使用 JIT。

建议

仅在另一个服务需要 JIT 时使用它。

在不同的 STA 中同步和激活

默认情况下,同步是开启的,而且大多数情况下应使用它。通过使用每个方法调用中的通道所提供的因果性 ID (CID),同步不仅保护组件防止并发,而且也防止重入。同步作为 COM SCM(服务控制管理器)的指示,标识哪个单元绑定到“单元”组件。SCM 谨慎地保护有限的 COM+ STA 线程池,设法减少封送处理的系统开销,并尽可能地将线程切换减至最低。因此,如果 SCM 知道两个“单元”组件将会在执行的逻辑线程中发生交互,那么 SCM 会试图将它们实例化得尽可能地相互接近,比如在同一个单元里。

标准 COM 线程规则规定,如果一个“单元”组件是从 MTA(多线程单元)、NA(中立单元)、或者远程线程实例化的,那么该“单元”组件将被绑定到进程的 Host STA 上。这不是个好主意,因为序列化和性能可能会降低,所以应尽可能避免发生这种情况。如果一个已配置组件使用同步(”Requires” 或者 ”Requires New”),将该组件绑定到其中一个 COM+ STA 池化线程会更加优化。如果一个“单元”组件被非 STA 线程实例化,使用同步是更可取的,也更容易发现问题

建议

对于所有的已配置“单元”组件使用 “Requires” 同步。该方法允许组件成为现有同步边界(或者为它新建一个)的一部分,并全部都被绑定到 COM+ STA 池化线程。另外,同步适当地保护一个“单元”组件以防止重入。

遵照每客户端一对象模型

如果对象不被共享的话,大多数的 COM+ 功能都能很好地工作。通过实例化组件、进行工作并随后立即释放它,COM+ 很好地工作着。另外,在更高的级别上,整个 n 层应用程序通常也更加可靠和快速。

本节中讨论这些主题:

STA 中的同步死锁

当共享对一个“单元”实例的引用时,将会有不止一种方法导致死锁的出现。描述死锁过程的详细讨论超出了本文的范围。如果共享 对象引用,即使只是使用 COM+ 同步支持,也可能会发生死锁。例如,如果一个因果性 ID (CID) 调入另一个 CID 同步边界,而每个 CID 都保持它自己的同步锁,就可能会发生死锁。如果持有同步锁的组件 ”A“ 做了一个 OOA 调用,并且在重入的过程中,另一个活动试图进入相同的由组件 “A“ 持有锁的同步边界时,可能会发生死锁。听起来很乱是不是?当您向对象传递对象引用并且使用同步时,这的确是会发生的。

建议

避免共享“单元”实例。如果使用 COM+ 同步,要特别注意避免死锁并尽可能遵循每客户端一对象模型。

共享对象

到现在,您应该理解每个客户端一个对象是理想的情况。即使在每个客户端一个对象的情况下,如果对象使用 Microsoft Win32 同步原语,阻塞仍然可能是多线程 MTA 里的一个问题。但是在单线程 STA 里,它绝对是致命的。共享的“单元”对象意味着能够有很多挂起的调用者通过它的单线程 STA 调用其方法。如果调用进入得比它们获得服务的快,或者如果“单元”对象锁住了全局数据,并进行了 OOA 调用,可能会发生阻塞、序列化以及死锁。在 MTS 和 COM+ 中都是这样的。不过在 COM+ 下,如果由于用来在同步边界内实施并发所的算法使方法调用要花较长时间来完成的话,这些问题会扩大。

建议

避免共享对任何对象(尤其是那些标记为“单元”的对象)的引用。

可能的话使用“Both”线程模型

在 MTS STA 活动线程池模型下,一般采用“单元”方法。对于 COM+ 中的 MTA 线程池,“Both” 是大多数情况下最好的选择。“Both” 在 COM+ STA 以及 MTA 线程池中都很成功。

MTS 常常不管调用者的“单元”类型就在 STA 线程上对组件进行实例化。如果是从 MTA、NA、或者远程调用者的线程激活,一个 “Both” MTS 组件始终被绑定到一个 MTS STA 池化线程。

在 COM+ 下,从一个非 STA 线程创建的组件被绑定到更符合需要的 COM+ MTA 线程池。如果组件被标记为“单元”,它将被绑定到 COM+ STA 池化线程,这是该情况下的一种次要选择。因此,要避免把非 STA 线程调用的组件开发成“单元”。而应该将它开发成 “Both”(若使用 Microsoft Visual Basic 版本 6.0,则您别无选择)。

要意识到在 MTS 下有一个微妙的差异,在某些情况下会导致 COM+ 中跨单元的封送。当一个对方法调用中 COM+ “Both” 对象的引用被传递到在相同的活动或同步边界中的“单元”对象时,就会出现这种情况。在 MTS 下,此对象被绑定到相同的 STA 池化线程,调用将是直接的。在 COM+ 下,如果组件是从非 STA 线程创建的,组件将被绑定到 MTA 并且将使用标准的跨单元封送产生任何来自 STA 的对该组件内部的调用。现在一个微妙而代价高昂的问题出现了,而且当共享对象时我们再次遇到了潜在的问题。

在 Microsoft Internet Information Server (IIS) 版本 4.0 和 5.0 下运行的 ASP 页面脚本在 STA 内执行。任何时候从 ASP 中运行 COM+ 组件,您都希望将该组件作为脚本激活器代码在同一 STA 中运行。通过 ”Both” 组件的“单元”方面来完成这一要求。“自由”方面允许组件在 DCOM 调用过程中,通过绑定到一个 MTA 中的线程来使用。因此,”Both” 再次成为最好且最灵活的选择。

另外,一个在 STA 中运行的“单元”组件,由于使用隐藏窗口和相对 LRPC(本地远程过程调用)而言的消息传递,意味着额外的系统开销。这是一个大可不必产生的性能影响。

如果在进程中,”Both" 组件常常在其调用者的单元中激活。对于尚未配置的组件,"Both" 是正确的选择,因为它也在调用者的 COM+ 上下文激活。

建议

对于大多数的 COM+ 组件,使用 "Both" 线程处理模型来利用 COM+ MTA 线程池并增加灵活性。尽可能地避免纯“单元”模型。

辅助功能注意事项

虽然本节并不属于从 MTS 到 COM+ 的直接映射,但是您可能会希望使用随后的 COM+ 功能以丰富应用程序的功能性和性能。

COM+ 对象池

首先,由于一个池化实例不能有任何线程关系,因此不能从 Visual Basic 6.0中使用 COM+ 对象池,对于 Visual Basic 6.0 组件亦如此。在整个池生存期,一个池化对象几乎总是不能从创建它的初始线程上被调用。因此,"Free" 是推荐的池化对象模型,不过 “Both” 同样能完成任务(它的 "Free" 方面)。尽管 ”Neutral" 也能完成任务,但这是一个非最优选择,这是由于为避免线程切换而会在所有 NA 中的调用上产生的系统开销造成的。池化对象不能跨越实例维护一个特定调用方的状态,该对象必须支持聚合,而且对象不能聚合 FTM(自由线程封送拆收器)。

通过使用对象池而节省下来的大开销,并不是和事实上避免创建请求的新实例所需的系统开销一样多。确切地说,这种节省是通过不必竞争来获取有价值的共享资源(如一个数据库连接)而得到的。如果要跨越一个类对象的实例使用可重用的资源,也许应该考虑 COM+ 对象池。使用 IObjectConstruct 接口,您可以将数据源名称 (DSN) 作为连接字符串传递给一个池化对象,而且该对象能打开这一连接。只要对象在池中,对象便保持连接的打开,这样在方法调用过程中数据库的访问很快。有关更多信息,请参见 HOWTO:Write a COM+ Pooled Object That Pools an ADO Connection (Q266690)

新的 COM+ 接口

以往在 MTS 下不可用的补充的功能,如今在 Visual Basic 以及 Microsoft Visual C++ COM+ 组件中可用了。例如,您能够使用 IContextStateIObjectContextActivity、以及 IObjectContextInfo 接口来执行下面这些任务:

  • 获取上下文以及活动 ID。

  • 在对象的上下文中手动操作字段。

  • 获取您所登记的事务 ID。

  • 不必调用 SetAbortSetComplete 即可手动设置事务的结果位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值