UML软件工程组织

探讨 ASP.NET 的可伸缩性
来源:msdn
Michèle Leroux Bustamante

http://www.idesign.net/(英文)

适用范围:

Microsoft® ASP.NET

Microsoft® Windows® 2000 Server

Microsoft® Internet Information Services (IIS) 5.0

Microsoft® SQL Server(tm) 2000

摘要:本文中,Michèle 将探讨某些可能影响 ASP.NET 应用程序可伸缩性的体系结构和设计决策。此外,她还会介绍怎样使用 Enterprise Services 和 MSMQ 来降低这些伸缩性问题带来的影响。

本页内容
引言 引言
多少线程会降低应用程序的速度? 多少线程会降低应用程序的速度?
如何缩放……下面给出几种方法 如何缩放……下面给出几种方法
伸缩性体系结构的小例子研究 伸缩性体系结构的小例子研究
结论 结论
参考资料 参考资料

引言

在二十世纪九十年代中期的 dot-com 鼎盛时期,当应用程序服务提供商 (ASP) 有望利用互联网的成功浪潮时,许多公司纷纷涌进该领域,并迅速取得了成果。除了随后令人遗憾的市场崩溃外,ASP 还需要处理其他问题,例如培训员工有效地建立和管理安全、可靠和高可用的操作。如今,特别是自从 Web 服务已成为多数应用程序的来源以后,启用 Web 的体系结构广为流行。ASP 以往的成长痛苦经历使各地商业企业都渴望成功实施 24x7 应用程序。当面向服务的体系结构 (SOA) 主题在各个组织间传播时,应用程序现在比以往具有更广泛的用户。抛出代码、发售产品和合手祈祷的日子一去不复返了。

因此,多数开发人员可能会接触到必须服务于不可预知连接数、会话数和页面请求数的企业应用程序的某个部分。获得成熟的编程技巧(包括组件设计、体系结构规划以及对使得应用程序可伸缩、可用和安全的相关方面的透彻洞察力)从来没有显得如此重要。如今它通常是决定业务甚至是商业成功与否的关键。但是,您知道他们所说的话,“如果您不是解决方案的一部分,那您就是问题的一部分”。所以,请允许我在描述有关使用 Enterprise Services、COM+ 和 MSMQ 伸缩 Microsoft® ASP.NET 应用程序的观点时,为您就一些可伸缩性的最佳方法作一下概述。

多少线程会降低应用程序的速度?

在您开始编写企业 Web 应用程序代码行之前,您应该先理解与 ASP.NET 处理模型、ASP.NET 如何与 IIS 进行交互以及处理每个唯一页面请求时有多少线程在运行相关的一些情况。此信息会帮助您决定如何构建应用程序、何时衍生非由线程池中取出的线程、何时使用异步消息传递以及何时将进程移至系统中的另一物理层。

让我们来举一个完全驻留于单个物理层上的 ASP.NE 应用程序的简单例子。请考虑以下两个配置:

Web 服务器配置 服务器应用程序

Microsoft® Internet Information Services (IIS) 5.0

Microsoft® Windows® 2000 Server、IIS 5.0、ASP.NET、Microsoft® SQL Server(tm) 2000

Microsoft® Internet Information Services (IIS) 6.0

Microsoft® Windows® 2003 Server、IIS 6.0、ASP.NET、SQL Server 2000

图 1 显示 IIS 5.0 如何分配请求以及默认情况下进程、应用程序域和线程如何管理往返。图 2 显示的是 IIS 6.0 的相同内容。


图 1. IIS 5.0 请求


图 2. IIS 6.0 请求

在 IIS 5.0 配置下,ASP.NET 资源请求由 inetinfo.exe 接收并传递到 ASP.NET 的辅助进程 (aspnet_wp.exe) 进行处理。IIS 6.0 通过低级的 http.sys 内核接收请求并直接将请求转发到 w3wp.exe 进程。在这两种情况下,结果都是驻留 ASP.NET 运行时的辅助进程。应用程序在应用程序域内运行,应用程序域具有一个 HttpApplication 对象池为每个请求提供服务。HttpApplication 对象负责加载任何已配置的 HTTP 模块,并负责加载处理单个请求的 HTTP 处理程序。辅助进程拥有一个线程池,该线程池被取出用来处理由池中的一个HttpApplication 对象操作的并发请求。当请求耗尽所有的线程池线程(默认配置通常是 25 个线程)时,线程池增加到最大配置设置或将请求排队。每个 IIS 应用程序获得属于它自己的具有自身 HttpApplication 池的应用程序域,但是,它们在 ASP.NET 辅助进程内运行并共享它的线程池。对于 IIS 5.0,单个辅助进程驻留在每个 CPU 中,但在 IIS 6.0 中可获得更大的伸缩性,因为在单个 CPU 上可分配更多的辅助进程。在以上任一配置下,在具有足够多并发请求的某一点,线程池可被耗尽从而导致请求排队。使用其他的 IIS、Window Server 和 ASP.NET 配置调整可以“吸出”附加吞吐量,但我只关注为请求服务的进程和线程。

假定应用程序依赖于数据库,处理 HTTP 请求的线程可能与数据库引擎通信,数据库引擎通常支持自身的连接池和处理负载的排队机制。数据库服务器需要非常重视伸缩性,这就意味着每秒监控插入和其他统计,例如插入占地、优化索引、调整连接池配置和最好由有经验的 DBA 执行的其他活动。

越过所有这些处理界限(Web 服务器、应用程序实例和数据库服务器)和许多产生用来处理请求的线程;您可能可以想象到性能会在某个点降低,除非我们找到分配工作量的方法。

如何缩放……下面给出几种方法

伸缩性与网络拓扑和硬件以及与软件体系结构同样相关。软件体系结构带给表的最大之处是使之能够从单一应用程序堆栈中吸取最大功能。既然您对组成单个往返的进程和线程数有更深理解,我将浅谈一下如何从那个线程模型中获得更佳性能和提高可靠性。

层和物理冗余

一个企业解决方案至少应当有两层。在两层方案中,Web 服务器层可以驻留……嗯……Web 服务器(通常是 IIS)、Web 应用程序和可能的业务和数据库访问组件。数据库服务器层允许您将数据库引擎的繁重负担卸载到另一个数据库服务器引擎,通常是更强大的设备。实际上,两层对于事务繁重的企业解决方案来说一般是不够的。可能有某些业务处理需要大量的系统资源,例如文件 I/O 操作、繁重的数字处理和集成系统调用。所以,传统上至少有三层,才能允许 Web 服务器集中处理较简单的请求和委派业务组件与分布式应用程序层交互。

不论应用程序体系结构如何,每层都应至少有一个冗余对等层。也就是指从防火墙、路由器和网络负载平衡器到部署在每层的机器上的所有内容,如图 3 所示。


图 3. 完全冗余系统

冗余与在 active-active (主动-主动)配置(是指可供进程会话使用的所有冗余机器)中的硬件负载平衡结合使在几个机器间委派请求成为可能。有了 active-passive (主动-被动)配置,单个机器能够处理所有的请求,而且一旦主机器出现故障,就可以等待激活被动机器。这样仍可以满足可用性目标,但显然无助于降低主机器的负载。

也应当配置网络体系结构中的所有物理层,以便可以添加其他机器来支持那一层上的横向伸缩。例如,如果应用程序层(应用程序的核心)开始达到内存、CPU 或其他昂贵资源的最大阀值,在该层添加机器可以提高吞吐总量。基于他们消耗的资源类型和该层上的设备类型,各层上可能有不同的横向伸缩阀值。例如,数据库层通常使用具有强大马力(附加内存、更多处理器等等)的服务器,因而也就更昂贵,也就很难成为能够广泛使用的横向伸缩性的候选者。需要进行文件 IO 或繁重的数字处理的应用程序服务器可能将繁重的工作卸载到另一个或更多的层上,这些(个)层可以方便地伸缩以满足峰值负载需要。卸载生成大报表、创建和保持文档或发送 SMTP 邮件的组件就是这样一个例子。

寻址减慢页面加载

撇开网络体系结构不谈,您一定希望应用程序尽可能快速地响应页面请求。默认情况下,页面请求由可用的应用程序域线程中的一个线程来处理。这也就意味着,当所有可用的应用程序域和相关的线程池被耗尽时请求可能会排队。较慢的页面请求(独占线程池中的一个线程)会不可避免地降低 Web 服务器的平均页面加载统计值,但如果已知一个特殊页面在往返过程中需要额外的服务器端处理,您就可以将该页面的处理卸载到从线程池中分离出来的新线程中。每个 Page 对象都实施 IhttpHandler,并且在默认情况下是同步处理的。但您可以在耗尽页面加载统计值的 Page 对象上实施 IhttpAsyncHandler 接口。您可以编写代码在自定义线程而不是线程池中的线程上调用页面处理,这可使应用程序域内的任何队列请求从线程池中提取线程并运行!请参阅这篇 MSDN 文章(英文),以获取关于异步页面处理程序的更多信息。

类似地,也可以异步调用 Web 方法来在 Web 服务往返过程中释放线程池。这可以通过实施异步设计模式的 Web 方法来实现(请参阅 MSDN 上的这篇文章(英文))。但是,释放线程池(仅生成其他自定义线程)的能力不能保证显著地提高性能。所有依赖于文件 I/O、数据库或其他成本可能较高的活动的页面或者 Web 方法都不应该自动设置为异步。在走这条路线之前,您应首先设计应用程序的体系结构,并监控页面加载统计值。原因是这些异步模式仍然在辅助进程中旋转另一个线程,导致出现影响性能的更多上下文切换。从释放线程池所获得的有限收益可用于异步消息传递,或通过同步或异步方式将组件活动卸载到系统堆栈中的另一层。

减少瓶颈

在每个页面加载背后都潜藏着潜在的处理瓶颈。在考虑应用程序工作流时,您有机会纠正应用使程序体系结构,以便避免性能缺陷和简化组件分布变更和维护工作。以下是一些应用程序处理方案和一些提高性能和可靠性的高级应用程序体系结构方法:

繁重的数据库负载。非常需要合格的 DBA 来调整性能优化活动以满足应用程序的要求(相信我,找到出类拔萃的 DBA 是难之又难的!)。然而,通过使用合适的硬件配置(包括镜像驱动器)在单独的层上部署数据库引擎减轻对性能的担忧也是可能的(见图 1)。

长运行操作。诸如数据库查询、插入包括大结果组、繁重的数字处理和远程调用这样的操作都可能导致消息列队,从而引起响应延迟。应为异步消息传递考虑这些活动。内存不稳定和服务器可能出故障,这些都是残酷的现实。为了降低在往返过程中丢失请求数据的风险,为了确保可靠处理数据和为了从 ASP.NET 辅助线程中卸载工作,您可以从具有 System.EnterpriseServices 的 .NET 框架中方便地使用 Microsoft 消息队列 (MSMQ)。

资源密集型特性。有些时候,我们不得不加速文件系统,例如生成报表或 PDF 文档最终需要持续输出文件。数字处理也可能是资源密集型过程,会消耗大量的内存和 CPU 周期。这些都是可能需要卸载到另一物理层的资源密集型特性的不同例子。借助于 System.EnterpriseServices 命名空间中可用的组件再次使用 MSMQ 和 COM+,您可以将工作卸载到可靠体系结构的其他物理层。

服务器停机情况。是的,确实会发生服务器停机情况,MSMQ 可以用几种方式帮助您恢复。首先,消息可被记录(序列化),因此如果服务器停机,一旦重启服务器这些将消息准备就绪并等待播放。其次,如果队列试图调用当前不可用的其他层上的组件或出现异常,消息在最后以最终的“死信”队列静止前,会通过一系列重试队列。当然,有很多方法对其进行配置,但是其推进是不丢失任何消息。

分布式事务。就应用程序层和组件体系结构谈了这么多,如果忽略掉管理分布式事务的需要,我可就算是“玩忽职守”了。幸运的是,COM+ 组件内置有利用 Microsoft 分布式事务处理协调器 (MSDTC) 的能力。

使用适当的网络体系结构和设备,结合多线程、消息队列、分布式应用程序处理以及松散联系的事件组合,您的应用程序将有能力更好地伸缩和提供客户期望的良好的可靠性。

在本文的余下部分,我将为您提供一个我开发的示例应用程序概述,该应用程序使用了应用方案中的一些概念。请考虑将此视为一个激发您解决一些伸缩性和可靠性问题(到目前为止我已使用合理的体系结构和组件设计讨论过了)的起点。

伸缩性体系结构的小例子研究

要推荐 ASP.NET 应用程序伸缩性理论上的最佳方法是很容易的,但谈到它的时候,为了决定在哪能最好地使用我讨论过的概念您就不得不评估一下每个应用程序的需要。请记住这些,我将带给您示例文件上传 ASP.NET 应用程序。该应用程序足够简单,不会用一堆详细的应用程序代码把您弄得一头雾水,但可以看出它具有一组用于当前或将来的伸缩性的易于确认的需要,所以我使用 Enterprise Services 并不完全是“预谋的”。

该示例是一个提供文件上传页面的 ASP.NET 应用程序,它允许用户上传带有描述性信息的文件。该应用程序允许用户通过 Web 浏览器上传文件,此处文件是永久性的,并在 FileTransactions 表中插入记录上传操作细节的记录。其他随文件上传提供的细节(例如标题和描述)记录在 FileUploads 表中相应的记录中。体系结构如图 4 所示。


图 4. FileUpload 应用程序体系结构

请求是同步处理的,并且业务组件依赖相同的层来处理文件系统和数据库交互。在以下部分,我将讨论如何使用 Enterprise Services 的几个特性来提高该应用程序的伸缩性和可靠性。如果您不熟悉 MSMQ 和 COM+,请将这看作是对他们的价值和本文中讨论的解决方案应用程序的高级介绍。这些方案是:

使用 MSMQ 将长运行的活动异步运行。

使用 COM+ 在层间分配活动。

使用 COM+ 事务确保系统在各层间的连续性。

异步消息传递,内置

MSMQ 是比异步活动的多线程伸缩性更好、更可靠的媒介物。这部分是因为消息队列可被序列化,因而也是容错的。但是,我们不要忘记消息队列也可以调用远程组件和离线队列消息(当这些组件或系统不可用时)。

在本代码示例中,调用客户端(Web 页面)不关心上传文件需要多长时间,并且在作业完成时无需通知。文件上传后必须将其保存到磁盘,并插入有关其他细节的数据库记录。此处我使用 MSMQ 实现异步消息传递。

FileUploadMgr 组件注册为 COM+ 应用程序,并配置为支持消息队列。当注册 COM+ 应用程序后,会为该应用程序创建默认专用队列(包括重试和“死信”队列),可以从计算机管理 MMC 管理单元中对其进行查看。UploadMgr 类继承 System.EnterpriseServices.ServicedComponent,并自动配置为应用程序的注册组件。您可以提供描述 COM+ 应用程序和组件属性的元数据,以便注册可以使用元数据来配置它们。我将解释一些我应用于每个注册组件及其程序集的 .NET 属性。

在示例的每个项目的 assemblyinfo.cs 文件中,以下属性应用到了程序集中:

[assembly: ApplicationName("FileUploadApp")]
[assembly: Description("分布式应用程序演示
接收上传文件并将它们记录为上传的应用程序
事务。 ")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationQueuing(Enabled = 
  true,QueueListenerEnabled = true)] 

该元数据配置 COM+ 应用程序名和描述,也设置应用程序以在服务器进程中运行、启用消息队列侦听器和队列进程。因为这些属性在这所有三个程序集中都作了设置,组件在同一 COM+ 应用程序下注册。

注意:通常 Enterprise Services问题(包括与注册有关的问题)在 GotDotNet.com 的 FAQ(常见问题解答)中都作了回答:http://www.gotdotnet.com/team/xmlentsvcs/esfaq.aspx(英文)。

通过在其他类型中包括元数据可以避免手动编辑 COM+ 配置设置。注册的组件继承 ServicedComponent,并且实现队列接口(如果消息队列支持该组件)。列队的组件实现使用由 InterfaceQueuingAttribute 设置的接口。该接口不能违背列队的组件要求(例如,返回不支持的值和输出/引用参数)。

以下是使 UploadMgr 成为列队组件的适用定义:

[System.InteropServices.InterfaceQueuing]
public interface IUploadMgr
{...}

public class UploadMgr: ServicedComponent,IUploadMgr
{...}

你可以使用组件服务配置管理单元,但下列批命令在 GAC 中正确地安装了 FileUploadMgr,并为 COM+ 对其进行注册:

gacutil.exe" /i fileuploadmgr.dll
regsvcs.exe" /c fileuploadmgr.dll

FileUploadMgr 是主要的业务组件,Web 窗体通过与其进行交互来上传文件。当用户回送到 Web 窗体时,以下代码将请求列队给 UploadMgr.UploadFileInfo()

         FileUploadMgr.IUploadMgr obj;
         obj=(FileUploadMgr.IUploadMgr)
            Marshal.BindToMoniker("queue:/new:FileUploadMgr.UploadMgr");
         obj.UploadFileInfo(this.txtTitle.Text, this.txtDescrip.Text, 
            filename, data);
         Marshal.ReleaseComObject(obj);

BindToMoniker 是一实用函数,其返回提供的名字对象引用的请求对象。在这种情况下,名字对象调用使用 progid FileUploadMgr.UploadMgr 注册的 COM+ 组件:即将其传递给 UploadMgr 接口,该接口支持队列,使用该接口对 UploadFileInfo() 的调用将被列队,可以返回往返。

这样做的结果是加快了页面的响应时间,但队列也为所有的上传事务提供了可靠的存储,从而确保在等待过程中不丢失任何消息。COM+ 应用程序有一个侦听器,配置用来播放队列中的消息,它将实例化 UploadMgr 组件并调用 UploadFileInfo()。如果系统变得不稳定,队列可在机器重启时得到处理,处理从队列中断处开始。而且,如果在方法调用过程中出现异常,则重试队列可用于不断尝试方法调用直到最后导致“死信”队列(管理员可在此处采取适当的措施)。

简而言之,给此应用程序体系结构带来的好处包括:

响应时间不受长运行操作的影响。

消息从不会丢失,可以重播直至成功。

可以将 COM+ 组件移到另一个物理系统层,它仍可以从头到尾对本地消息队列起作用,而不会影响应用程序代码。

如果偏爱同步调用该功能,通过直接调用 COM+ 组件仍可以提高性能,但需将其配置到另一队列以便将工作卸载到另一系统上并减少 Web 服务器上的文件 IO 连接。

分布式和非分布式完全一样吗?

注册 COM+ 组件调用应用程序体系结构层的其中一个优点在于具有在不同拓扑内分布这些组件以满足性能需要的能力。图 5 和图 6 演示在示例组件的单一层调用之间切换与将业务处理组件移到中间应用程序层的对比。


图 5. 一个层上的业务处理


图 6. 独立层上的业务处理

如果伸缩性为了平衡负载分配需要将文件 IO、数据库和其他业务逻辑分发到中间应用程序层,可以远程部署和由本地消息队列调用同一 COM+ 组件。实际上,如果与系统堆栈中的其他层比较需要以不同的比例横向缩放,将文件 IO 分布到另一物理层上也是可能的。预先设计应用程序支持这类灵活性意味着从图 5 中的体系结构转移到图 6 中的体系结构无需在它在产品服务器中发挥作用并开始获利之前,进行深层次的开发和 QA 循环。

事务变简单,分类

除了实现消息队列和使用 Enterprise Services 注册组件带来的伸缩性好处以外,事务支持也使作为原子事务处理一部分的组件中特定的活动调整成为可能,即使这些组件是分布式的也有可能做到。此示例利用事务来调整数据库升级(使用成功的文件档案)。UploadFileInfo() 执行以下操作:

使用唯一上传事务标识符将“挂起”事务保存到 FileTransactions 表中。

通过 FileUploadMgr 组件将上传的文件存档到文件系统中。

如果 #2 成功,从初始创建的 FileTransactions 记录中引用相同的唯一事务标识符将文件上传细节记录到 FileUploads 表中。所有的数据库操作由 FileUploadDALC 组件处理。

如果 #2 和 #3 成功,使用“完成”状态更新 FileTransactions 记录。

如果 #2 和 #3 失败,使用“失败”状态更新 FileTransactions 记录。

步骤 #1 始终应当执行。步骤 #2、3 和 4 需当作一个原子事务,如果那个事务失败,应当执行步骤 #5(更新 FileTransaction 为“失败”)。所以,我们应在这里看一下如何才能登记事务中的这些 COM+ 组件,而不用费事。

运行 COM+ 的机器也会将 Microsoft 的分布式事务处理协调器 (DTC) 作为服务运行。DTC 管理与事务相关的活动,包括与资源管理器(如 SQL Server)交互和收集作为两相提交 (2PC) 一部分的事务提交或事务回滚的投票。在事务的第一阶段中,登记资源准备提交。第二阶段发生在实际执行提交或回滚操作时,因为收集所有的投票后 DTC 将通知资源管理器。您可以使用自动的事务(声明属性)或手动事务(需要做大量工作!)在事务中登记托管组件。自动的事务是指将 .NET 属性应用于在调用时参与事务的组件。也可以将属性应用于方法以便在要提交或回滚的当前事务上下文中自动投票。资源管理器(如 SQL Server)通过与 DTC 交互来接收 2PC 事件的通知,并具有内置机制处理每个阶段。

通过使用默认设置将 TransactionAttribute 应用于 UploadMgr,从而将 TransactionOption.Required 应用于该属性的 Value 属性,所以该组件将成为所有新事务的根组件,或者参与调用事务上下文(如果适用)。所以,将其应用于 UploadMgr 意味着 UploadFileInfo() 成为新事件的根。那也同样意味着所有由 UploadFileInfo() 生成的对其他组件的调用(保存文件和写数据库记录)将被视为单一事件并在调用失败时回滚,只要这些组件也支持事务。更具体地来讲,如果其中一个调用失败或者文件保存操作失败,将会回滚所有数据库插入。

使用这种方法的问题在于,当我其实想使步骤 #2-4 成为原子时,步骤 #1-5 却被视为原子。结果是没有永久的 FileTransaction 记录表明发生过上传操作(用于报告目的)。怎样才能实时报告挂起、完成和失败事务呢?如果出现异常且回滚了所有的数据库插入,怎样才能更新 FileTransaction 记录以具有“失败”状态呢?这无法从 Web 窗体上驱动,因为整个进程异步委派给了 COM+ 组件。这也无法在 UploadFileInfo() 方法内部实现,因为在终止的事务上下文中,所有的资源托管活动(本例中指数据库)都会被回滚。所以,即使捕获了由失败的事务抛出的异常也不会执行任何数据库插入。

这里的问题是自动的事务(即 TransactionAttributeAutoCompleteAttribute)不为我们提供对如何提交或回滚的详细控制。我们有一个使用自动的事务、用于控制其他复杂进程的简单编程模型。然而,我们无法嵌套事务或将事务的初始化应用于类的特殊方法上,也无法在运行时动态地取消部分代码参与事务。通过手动编写事务代码,我们可以对其进行控制,但是带来的好处远比不上引入复杂性和错误所冒的风险,更别说是降低工作效率了。

为了利用自动的事务且仍能达到步骤 #1-5 中的目标,我对代码作了一些修改。事务根最初在 FileUploadMgr 上,但我在相同的程序集创建了一个名为 UploadUtil 的工具类并将它注册为参与 COM+ 事务的服务组件。UploadMgr 按期望的顺序驱动调用 UploadUtil 方法的进程,如下所示:

   public class UploadMgr: ServicedComponent,IUploadMgr
   {
      public void UploadFileInfo(string title, string descrip, 
         string filename, byte[] data)
      {
          
         UploadUtil util = null;
         int transId = -1;

         try
         {
            // 记录挂起事件记录
            util = new UploadUtil();
            transId = util.CreateFileTransaction();

            // 处理文件上传
            filename += ".trans" + transId.ToString();
            util.RecordFileTransaction(transId, title, 
              descrip, filename, data);

         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.ToString(), "事务失败");

            // 记录失败的事务记录
            if (util != null)
               util.RecordFailedTransaction(transId, 1);
            throw;            
         }
      }
}

调用 UploadUtil 方法会初始化新事务,因为组件支持自动的事务,且每个方法可以自动处理投票(由于可应用的方法上有 AutoCompleteAttribute):

   [Transaction]
   public class UploadUtil: ServicedComponent
   {
      [AutoComplete]
      public int CreateFileTransaction()
      {
         FileUploadDALC.IFileUpload obj = new FileUploadDALC.FileUploadDb();
         
         int transId = obj.CreateFileTransaction();
         return transId;
      }

      [AutoComplete]
      public void RecordFileTransaction(int transId, string title, 
        string descrip, string filename, byte[] data)
      {
         
         FileArchiveMgr.IArchiveMgr filesaver= 
            new FileArchiveMgr.ArchiveMgr();
         filesaver.SaveFile(title, descrip, filename, data);

         FileUploadDALC.IFileUpload obj = new 
           FileUploadDALC.FileUploadDb();
         obj.SaveFileInfo(transId, title, descrip, filename);

         obj.UpdateFileTransaction(transId, 2);
            
      }

      [AutoComplete]
      public void RecordFailedTransaction(int transId, int status)
      {
         FileUploadDALC.IFileUpload obj = 
           new FileUploadDALC.FileUploadDb();
         obj.UpdateFileTransaction(transId, status);
         
      }

   }

当将异常抛到 UploadMgr 组件时,异常处理代码发布一个新事务,使用当前事务标识符的“失败”状态来更新数据库。

RecordFileTransaction() 具体执行那些应为原子的步骤。调用 ArchiveMgrFileUploadDb 方法会登记那些组件以便参与调用事务上下文。但是,既然文件系统不是资源管理器,在回滚时就不会执行任何操作。

   [Transaction]
   public class ArchiveMgr: ServicedComponent,IArchiveMgr
   {
      [AutoComplete]
      public void SaveFile(string title, 
         string descrip, string filename, byte[] data)
      {...}
}

      [Transaction]
      public class FileUploadDb: ServicedComponent, IFileUpload
{
         [AutoComplete]
         public int CreateFileTransaction()
         {...}

         [AutoComplete]
         public int SaveFileInfo(int transId, 
           string title, string descrip, string filename)
{...}

         [AutoComplete]
         public void UpdateFileTransaction(int transId, int status)
         {...}

}

COM+ 事务的一大优势在于支持分布式方案,如图 5 和 6 中显示的那样。

结论

如果本文发挥了作用,您现在对于程序员在构建伸缩的和可靠的应用程序时面临的设计挑战将有深层次的理解。希望能激发那些不熟悉 Enterprise Services、MSMQ 和 COM+ 的学习者学习更多的东西,以便在下一个项目的设计阶段应用新学到的知识。我在下面提到的一些资源应该能够为您的学习提供一些帮助,潜心研究这些概念,会为解决应用程序的伸缩性问题作好较好的准备。

参考资料

.NET 组件编程

COM 和 .NET 组件服务

关于作者

Michèle Leroux Bustamante 是 Idesign Inc 的首席软件设计师、Microsoft 圣地亚哥地区总监、Microsoft XML Web Services MVP 和 BEA 技术总监。她具有超过十年的使用 Microsoft® Visual Basic®、C++、Java、C# 和 Visual Basic.NET 开发应用程序经验以及使用相关技术(例如 ATL、MFC 和 COM)的经验。在 Idesign,Michèle 提供培训、指导、和高端咨询服务,她主要致力于 ASP.NET、Web 服务和互操作性、以及 .NET 应用程序伸缩和安全体系结构设计。她是国际 .NET 演讲人协会 (INETA) 会员、常务会议代表、SD Web Services track 会务主席,并经常在几个主要技术期刊上撰文。Michèle 也是加利福尼亚大学圣地亚哥分校继续教育学院的 Web Services 应用程序顾问、SearchWebServices.com 的 .NET 专家。要联系她,请发电子邮件到 mlb@idesign.net 或访问 http://www.idesign.net(英文)和http://www.dotnetdashboard.net(英文)。

转到原英文页面


 

版权所有:UML软件工程组织