65.9K
CodeProject 正在变化。 阅读更多。
Home

COM+ 对象池

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (9投票s)

2000年6月1日

viewsIcon

168295

COM+ 对象池服务的介绍

前言

我将在一个分为两部分的系列文章中介绍 COM+ 对象池服务。第一部分将介绍该主题并描述其工作原理。此外,它还将规定可回收对象的要求,并描述何时适合使用此服务。

第二部分将重点介绍使用 Visual C++ 和 ATL 开发可回收对象。还将介绍如何部署解决方案以及监视可回收对象的统计信息。

术语

理解 COM+ 应用程序的概念非常重要。Platform SDK 文档中的正式定义如下:

"COM+ 应用程序是组件服务的主要管理和安全单元。该应用程序是一组 COM 组件,通常执行相关功能。"

对于熟悉 MTS 的人来说,这类似于 MTS 包。除非另有说明,本文档中的“应用程序”一词均指 COM+ 应用程序。

引言

对象池是一项在 MTS 中有所记载的服务,但在 COM+ 中才得到全面支持。MTS 词汇表将池定义为“基于使用预分配资源集合(如对象或数据库连接)的性能优化。”它还提到池化可实现更有效的资源分配。

虽然上面 MTS 的定义对于 COM+ 对象池是有效的,但后者在这方面有一些真正伟大的创新。这些将在本文中重点介绍。

可以声明性地配置组件,以指定其是否应被回收,以及指定各种池的特性。在 COM+ 的 Windows 2000 实现中可用的特性包括:

  • 最小池大小:这指定了池的低水位标记,默认为“0”(零)。
  • 最大池大小:这指定了池的高水位标记,默认为“1048576”。
  • 创建超时(毫秒):这指定了等待以满足客户端创建池中对象的请求的时间,默认为“60000”毫秒。一旦池中的对象数量达到高水位标记,其他客户端请求将不得不等待,直到池中的对象可用。由于此等待时间不确定,因此指定此特性可以更好地控制客户端等待获取池中对象的时间。

此外,还可以声明性地指定构造函数字符串,以自定义可回收对象以适应特定任务。例如,数据库连接字符串或日志文件名可以指定为构造函数字符串。有关此功能的更多信息,请参阅本文后面的“使用构造函数字符串与可回收对象”部分。

下图显示了 IIS Web 应用程序管理器组件的“激活”选项卡。应注意,所有这些功能在 COM+ 中都是新的,在 MTS 中不可用。

读者应从本文中了解 COM+ 对象池可用于以下一项或两项:

  • 提高应用程序的性能和可伸缩性
  • 管理资源

在适当的情况下,通过使用对象池可以显著提高 COM+ 应用程序的性能和可伸缩性。这是可能的,因为:

  • 最小数量的对象在应用程序启动时,但在任何客户端请求到来之前,都会预先分配并初始化。此数量在“最小池大小”特性中指定。
  • 任何耗时的初始化和资源获取都与对象为客户端执行的实际工作分离开来。
  • 由于对象得到理想的重用,昂贵且稀缺资源的获取成本会在多个客户端之间摊销。

资源管理主要通过在管理上配置“最大池大小”特性来实现。这是对象池的一个看似简单但功能极其强大的特性。最简单的例子是只保持您有多少许可证就可以打开多少数据库连接。这与 MTS 形成鲜明对比,MTS 不限制创建对象的数量,导致在对象开始因稀缺资源(例如数据库连接)的可用许可证全部用完而失败的情况下。

对象池的工作原理

应用程序要么通过在 COM+ 资源管理器中右键单击其上下文菜单选择“启动”来显式启动,要么由于其中一个组件的创建请求而被隐式启动。对于应用程序中已配置为池化的每个组件,COM+ 将在单独的池中维护和管理其实例。

池是同质的,这意味着池中的所有对象都具有相同的 CLSID。任何未使用的可回收对象都可以返回给任何客户端,但对于事务性对象,COM+ 会首先尝试扫描池以查找已与该事务关联的对象。有关详细信息,请参阅平台 SDK 文档。

应用程序中的每个池都填充了“最小池大小”特性中指定的实例数量。因此,理想情况下,在满足第一个客户端请求之前,每个池都将准备好配置数量的对象并等待。请注意,我使用了“理想情况”这个词,因为某些或所有对象可能由于某些错误而无法创建。

随着对象创建请求开始涌入,它们将按先到先服务的原则从池中满足。如果没有可用的可回收对象,并且池尚未达到“最大池大小”高水位标记,COM+ 将为请求的客户端创建一个新对象。在将对象引用交给请求的客户端之前,该对象会被激活,以便它可以执行任何每个客户端的初始化。

一旦达到高水位标记,所有后续的对象创建请求将被排队,等待池中的一个对象可用。池中已激活和已停用的对象数量永远不会超过“最大池大小”值。

如果任何请求未在“创建超时(毫秒)”特性中指定的时间内得到满足,则会向客户端返回错误(E_TIMEOUT),表明请求超时。此条件如何处理完全取决于客户端应用程序。

当客户端释放对对象的引用时,COM+ 会停用该对象并尝试将其返回到通用池。有关激活和停用过程的详细信息将在“可回收对象的状态管理”部分介绍。

回收 COM 对象的要求

COM 对象必须满足某些要求,COM+ 才能为该对象提供对象池服务。这些要求将在接下来的几段中进行描述。

对象必须配置为对象池化

这可以通过在 COM+ 资源管理器中对象属性的“激活”选项卡上选中“启用对象池”复选框来完成。

对象必须没有线程亲和性

由于每次激活可回收对象时都可能在不同的线程上调用它,因此它不应表现出任何线程亲和性。这意味着对象的 ThreadingModel 必须标记为 Both、Free 或 Neutral。这也意味着对象不应使用任何线程局部存储(TLS)来保存状态。

对象必须是可聚合的

如“可回收对象的状态管理”部分所述,当 COM+ 激活可回收对象时,它会创建一个聚合体来管理该对象的生命周期并调用 IObjectControl 的方法。因此,对象必须是可聚合的。

对象不应聚合 Free Threaded Marshaller (FTM)

COM+ 使用拦截来提供对象池等服务,而 FTM 基本上绕过了 COM+ 代理存根机制。因此,可回收对象不能聚合 FTM。

对象必须(几乎)无状态

为了使可回收对象能够被重用以服务不同的客户端,当它被返回到通用池时,它不能持有任何客户端特定的状态。它通常会有一些跨客户端重用的状态,例如在初始化期间获取的任何稀缺资源。如前面“可回收对象的状态管理”部分所述,可以通过实现 IObjectControl 接口来管理每个客户端的状态。

对象可以有选择地实现 IObjectControl

对象池不要求可回收对象实现 IObjectControl。因此,满足上述所有要求的旧版 COM 对象仍然可以被回收。在这种情况下,旧版 COM 对象将在达到最大池大小时创建,因为每个实例都被假定为可回收的。

这是使用旧版 COM 对象管理资源的好方法。如果性能和可伸缩性是使用此服务的真正原因,那么实现 IObjectControl 以启用生命周期管理是有意义的。

事务性对象(同时也进行回收)必须实现 IObjectControl。

可以使用哪些开发环境?

Microsoft Visual C++ 6.0

此环境允许创建满足上一节所述要求的 COM 对象。这适用于使用 Active Template Library 或原始 C++(类似于 Kraig Brockshmidt 在“Inside OLE”中的方法)创建的 COM 对象。

据我所知,无法将 MFC 用于此目的,因为 MFC 类不是线程安全的,这是如果线程模型为 Both、Free 或 Neutral 的主要要求。如果任何读者知道如何以可接受的编程复杂度实现这一点,我将很乐意修改此段以反映这一点。

Microsoft Visual Basic 6.0

由于 Visual Basic 6.0 只能创建 Single 或 Apartment 线程的 COM 对象,因此这些对象自动被排除在外,无法利用 COM+ 对象池服务。

正如 Steve Ballmer 在 VBITS 旧金山大会上的 2 月 15 日主题演讲中所述,Visual Basic 的未来版本很可能支持开发 Free 线程的 COM 对象。这些对象是否能满足对象池的所有要求,这个问题还有待预览版本可用后才能解答。

Microsoft Visual J++ 6.0

我从未在 COM 开发中使用过此环境,因此无法验证它是否可用于开发可回收对象。Windows 2000 RC2 的 Platform SDK 确实有一个 Java 示例,这让我相信该环境可用于开发可回收的 COM 对象。

对象池并非适合所有人

对象池经常被误解为所有性能和可伸缩性问题的“万能药”。本节的标题旨在提醒读者注意这一事实,并提供有关何时使用此服务的实用指南。

具体来说,只有当以下条件满足时,对象池才有效:

  • 池中的对象是同质的(由 COM+ 强制执行)
  • 对象的初始化成本很高,并且对所有客户端都是统一的
  • 对象在初始化期间获取并持有稀缺资源(如套接字或数据库连接)。
  • 客户端简短且连续地使用可回收对象。

必须通过测试客户端应用程序在有或没有池化的情况下使用该对象来彻底评估使用对象池带来的好处。另一个重要考虑因素是确定各种池特性的有意义值。'最小池大小'、'最大池大小'和'创建超时'的值会因应用程序而异。影响这些值的一些因素是硬件配置、可用的稀缺资源、平均客户端数量、平均客户端访问时间、最大客户端数量和预期响应时间。

更多详情…

接下来的两个部分描述了对于那些只想要 COM+ 对象池概述的人来说可能不感兴趣的主题。因此,我将它们分开了,放在文章的末尾。

可回收对象的状态管理

可回收对象的通常状态包括:

  1. 它在创建期间获取并在其整个生命周期内持有的稀缺资源,以及
  2. 它在特定客户端的上下文中激活时获取的状态。

为了更好地控制可归类为上述第 2 类状态,可回收对象应实现 IObjectControl 接口。MTS 程序员会熟悉此接口,事实上,它具有与 MTS 版本相同的 IID(我通过查看 VC++ 6.0 所附带的 MTX.h 和 Platform SDK 所附带的新 COMSvcs.h 进行验证)。

COM 对象不必实现此接口即可被回收。此规则的例外情况是,如果可回收对象也恰好是事务性的,在这种情况下,将使用接口的 CanBePooled 方法来确定其所持资源的状态。如果 COM 对象未实现 IObjectControl,那么在达到最大池大小时会创建该对象的实例,因为每个实例都被假定为可回收的。

当创建可回收对象时,COM+ 会将其聚合到一个 COM+ 对象中,然后该对象通过在其生命周期的各个实例中调用 IObjectControl 方法来管理前者。重要的是要理解,只有 COM+ 可以查询并调用此接口的方法。如果客户端尝试查询此接口,聚合实际可回收对象的 COM+ 对象将返回 E_NOINTERFACE。

IObjectControl 接口派生自 IUnknown,并定义了三个方法,如下文所述。

Activate:当通用池中的对象即将被返回给客户端并在特定的 COM+ 上下文中激活时,将调用此方法。这发生在调用任何其他方法之前。这是实现任何客户端和上下文特定初始化的好时机。

Deactivate:每当客户端释放对象时,都会调用此方法。如果对象也经过 JIT 激活,则在停用时会发生这种情况。任何客户端和上下文特定的清理通常在此方法的实现中执行。

此方法返回“void”(或无),因此无法用于确定停用是否失败。CanBePooled 方法(接下来介绍)是 COM+ 了解停用是否成功以及对象是否可以返回通用池的方法。

CanBePooled:在调用 Deactivate 方法后调用此方法,它允许对象通知 COM+ 它是否可以返回通用池。对象应监视其内部状态以确定它是否可以重用,并从此方法返回 TRUE。重要的是要确保对象以通用状态返回到池中,而不会持有任何客户端特定信息。

使用构造函数字符串与可回收对象

COM+ 允许参数化对象构造,其中可以在创建时将文本字符串传递给对象。这允许开发人员编写一个通用组件,然后通过 COM+ 资源管理器为每个安装配置该组件,方法是为其指定一个构造字符串。最经典的例子是指定对象在初始化时使用的数据源名称(DSN)。

COM 对象属性中的“激活”选项卡有一个“启用对象构造”复选框,只有当对象实现 IObjectConstruct 接口时才可用。选中后,可以在“构造函数字符串”编辑框中输入文本字符串。

COM+ 通过 IObjectConstruct 接口向组件的实例提供构造函数字符串。当创建组件实例时,COM+ 会查询此接口并调用其 Construct 方法,传入一个 IObjectConstructString 接口。组件实例随后可以通过 IObjectConstructString::get_ConstructString 访问构造函数字符串并适当地使用它。

值得注意的是,此功能不限于可回收对象,但可以在可回收对象中有效使用,以实现更精细粒度的回收和资源重用。Platform SDK 文档概述了创建多个不同的组件,这些组件除构造函数字符串和 CLSID 外都相同,以维护可供不同客户端组使用的连接对象池。我个人不确定我是否喜欢这种方法,并希望尝试其他技术来实现相同的行为。

摘要

本文旨在概述 COM+ 对象池服务。它清晰地阐述了可回收 COM 对象的要求,并提醒读者对象池并非实现性能和可伸缩性改进的“银弹”。

第二部分将深入介绍利用此服务的 COM 对象的开发以及用于对其进行严格测试的客户端应用程序。

© . All rights reserved.