使用 Microsoft .NET 构建分布式应用程序
Duncan Mackenzie
Microsoft Developer Network
2001 年 12 月
摘要:本文说明了异步处理的优点,并讨论了在您自己的系统中实施这种工作流可以采用的三种方法。(21 页打印页)
简介 | |
异步处理的优点 | |
更快的响应时间 | |
负载平衡 | |
容错能力 | |
断续连接的系统 | |
异步系统的问题 | |
对状态进行通知或轮询 | |
超时处理 | |
补偿逻辑 | |
实施策略 | |
利用 SQL Server 构建自己的工作流 | |
使用 .NET 和 MSMQ 处理工作流 | |
让 BizTalk 替您处理工作流 | |
选择方案的比较 | |
小结 |
在行业应用程序中出现的许多处理并非都能够即时执行。例如,验证信用卡在某些时候就需要十秒钟的时间。您在本地商店排队的时候,十秒钟过得很快;但在电子商务领域,十秒钟却非常漫长。如果您的 Web 站点或其他应用程序闲置这么长时间等待对客户的信用卡进行验证,那么您处理大量并发用户的能力将大大降低。
因此,对于运行时间相对较长的处理而言,无论它们需要十秒还是需要十天,都应当断开与您的应用程序的连接而以异步方式运行。以异步方式运行某个处理意味着,发出此调用的系统并不需要等待该请求执行完毕;请求发出之后,调用就立即返回。这种处理方式有许多优点,但最主要的结果是,它切断您系统中不同处理之间的连接,让它们以不同的速度运行。一套断开连接的、或者去耦的系统有助于在分布及伸缩方面取得最大的灵活性。
定单处理系统从 Web 站点或其他公司等外部来源接收定单,它最适合于采用异步处理方式。如果对这样的系统进行了去耦处理,在传入的定单量很大时定单会堆积起来,但处理的其余部分不需要一定按同样的速度进行操作。前端系统可以尽快地接收定单,而此处理的其余部分可以在定单量较少时追赶上来。在系统组件之间采用异步连接方式能够产生均匀效果,将变换不定的输入流转换为一致的请求流程进行处理。
如果不在某种程度上使用异步处理,任何电子商务应用程序都不可能正常发挥作用,因此,对这种体系结构很少有异议,但是我们仍有必要讨论其积极方面和消极方面。
异步体系结构的主要优点包括:
• |
前端处理(通常是您的 Web 页)响应更快,客户会认为这是一个运行速度较快的系统 |
• |
提供了用来提出负载平衡请求的简单方式 |
• |
提供了容错能力 |
• |
支持断续连接的系统 |
其中的每个优点都是通过异步模式对您的应用程序中不同部分进行去耦处理的结果。要让某个处理具有异步特点,就必须建立某种形式的队列来保存挂起的请求,该处理中的每一个步骤都只能与这些中间队列进行通信,而不能直接与其上一步或下一步进行通信。
图 1. 利用队列实现去耦操作
第一个优点,即更快的响应时间,是由于客户(这可能是正在访问您 Web 站点的某个人,也可能是另一个公司的计算机系统)不需等待任何定单处理过程开始进行的结果。在同步系统中,用户在整个操作(例如提交定单)结束之后才得到响应。
图 2. 同步操作的累积滞后时间
在异步系统中,提交定单之后,客户的延迟时间仅仅是将该定单传递给处理中的下一步所花费的时间。在某种程度上,这样的更快响应时间只是一种假象,因为客户收到响应时该处理并未真正完成,但客户不需要再等待了,这是重要的优点。
图 3. 在异步模式中滞后时间缩短
在接收高流量通信的系统中,人们常常希望将负载分布到多台服务器上,并且还希望根据需要调整这种分布以适应计算机数量的变化。有多种不同的方法处理系统的负载平衡问题,但异步处理所要求的基础体系结构能够在不增加额外软件的情况下轻松地提供灵活的负载平衡能力。
如上所述,在异步系统中,需要某种形式的中间存储或队列来存储步骤之间的请求。当一份定单完成了某步骤中的处理工作时,它就被放到队列中等待进行下一步处理。当下一个步骤准备好处理另一份定单时,它就从这种挂起请求列表中抓取一份定单。要完成这样的系统中负载平衡的实施,只需要增加计算机的数量,由它们处理挂起列表中对步骤 B 的请求。
图 4. 在处理步骤 B 的节点群集之间实现负载平衡
采用中间队列之后,在负载平衡和可伸缩性方面都获得了很大的灵活性。在该系统的前端或后端都可以放置任意数量的计算机,而且这种灵活性适应于任何一个处理步骤。您可以在每一个步骤中使用适当数量的硬件对系统的性能进行微调,也可以在一台计算机上将多个步骤结合在一起进行处理。
异步体系结构可以让您的系统具有容错能力,这样,即使在处理中出现中断,整个系统也不会崩溃。对灵活的负载平衡提供支持的功能同时也就是对容错能力提供支持的功能。如果某个软件或硬件故障删除了某个处理步骤,请求执行该步骤的那些挂起请求就在队列中等候直至该服务被恢复。这对处理中先前的步骤并不产生什么实际的影响,尽管总体处理时间可能由于故障而延长。如果遵循了上一节关于负载平衡所讨论的技术,很可能仅仅减缓某一步骤的处理,但并不会停止。同样的功能也可以通过使用群集方式来提供;群集可以在不进行任何负载平衡工作的情况下提供故障转移能力。
图 5. 异步系统能够容忍一个或多个节点出现故障
在负载平衡的系统中,处理同一步骤的其他服务器可以继续从队列中截获请求;如果各服务器都已经以接近峰值的状态运行,整个系统的性能将下降。
注意 尽管采用请求队列可以提供容错能力,但队列本身可能成为关键的故障点。用于确保这些队列可靠性的方法依赖于实施队列时所采用的特定技术,但一般都涉及故障转移群集以及将信息写入某个永久性的存储设备中,例如写入数据库中。
这种使异步系统具有容错能力的行为也同样能够让异步系统在无需始终连接所有工作流组成部分的情况下正常运行。在异步系统中,工作流中的某个阶段可能由业务合作伙伴来进行处理。而您的系统与合作伙伴的系统之间的连接有可能是间断的,或者仅在需要时才连接。因此,异步功能可以将不可靠通信链接的影响降至最低程度,而且还可以实现更经济的系统操作,因为它将通信资源的占用减至最少。
在断续连接的系统中,某个合作伙伴可以连接工作流过程并将一个或多个请求置入工作流过程中排队,也可以接收某一具体步骤的处理结果并随后在自己的系统中进行处理。异步处理方式让系统之间相互独立;如果系统 A 与系统 B 能够在同一时刻相互连接,那很好;但如果不能在同一时刻连接,它们以后也能够进行通信而不会有任何麻烦,因为信息会被存储起来,直至与接收者接通。
图 6. 使用中间队列能够支持断续连接的系统而不需要任何专门编程
断续连接的系统引入它们自己的一套体系结构决策,包括连接的频率、在连接时期之间对请求进行批处理、处理失败的连接尝试,等等。需要这种系统的常见场合是,必须与某个外部合作伙伴打交道以处理付款或处理定单实现;对于需要采用拨号连接而并不采用网络链接的所有情况,也需要这种系统。由于支持了断续连接,在资源总可以获得但连接数量受到限制的情况下,系统可以将对资源的占用减少到最低程度;限制连接数量的原因可能是许可证、配置或系统容量方面的限制——例如数据库、FTP 服务器以及带有服务广告协议 (SAP) 等其他后端系统的会话。
在同步系统中,调用处理要等待调用返回之后才继续向下执行;虽然这对性能和系统响应有负面影响,但它也有一些优点。在调用确实完成之后,它可以一同返回某种形式的状态消息,例如处理成功或处理失败。一个简单的例子可以说明此问题:将新定单插入数据库中,同时获得数据库生成的新定单 ID。如果对数据库进行同步调用(或许通过某个组件进行,由该组件处理实际的数据库工作),可以立即发回该定单的 ID,也可以表明该定单是否已经成功地添加到数据库中。但在异步系统中,虽然发出了插入定单的请求,但是实际的插入动作却未同时发生,因此,此时无法返回数据库生成的 ID,系统也不知道是否成功插入。获得已提交请求的状态以及创建 ID 这两个概念是紧密联系的,因为任何异步形式的状态跟踪都需要一个唯一的 ID。
生成跟踪 ID
在异步系统中工作可以采用多种方法获得跟踪 ID,但在选择这些方法时,必须牢记对系统进行去耦处理这一目标。可以在对请求的提交进行处理时同步生成 ID,由此获得 ID 并在随后以异步方式将该请求传递给处理的其余部分。但这种解决方案有损于采用异步处理所带来的优点,因为它至少会将请求的提交与请求处理的第一步紧密耦合在一起。
另一种方法是,由提交定单的系统生成 ID,这样可以保持系统的异步特点,但却丧失了在一个位置生成唯一 ID 的简单性。为了保证提交方生成的 ID 具有唯一性,通常采用两种方法:
• |
以随机方式或半随机方式生成 ID,通过随机数长度来保证唯一性,或者根据系统中唯一的硬件组件生成某个数码来保证唯一性(GUID 常被用于此目的) |
- 或者 -
• |
首先获得在单个提交系统中唯一的 ID,然后将此 ID 与提交方的标识符一同发送,从而生成一个组合的唯一 ID。 |
我倾向于选择第二种方法,因为它与处理采购定单 (PO) 所采用的(现在仍在使用)一般处理很相似,此类公司采购定单常以批处理方式提交;这样的话,与现有系统协同工作时就很容易转换。提交方,即 PO 情形中的公司,拥有他们自己的系统,可以生成唯一的 PO 编号,然后他们将该编号与其公司的标识符(既可能是客户 ID,也可能是系统 ID)一同提交。在提交方需要确定特定定单的状态时,他们也将这些信息组合起来使用。在接收端,仍然可以在定单处理系统中生成一个唯一的 ID 并在内部使用,但客户的 PO 编号并不删除。
图 7. 来自提交系统、含有引用 ID 的传入消息
状态跟踪
如上所述,必须拥有唯一的 ID 才能在异步系统进行状态跟踪。那么,如果有了该 ID,如何跟踪请求的状态呢?异步系统中的状态跟踪通常采用下面两种形式之一:或者向原始调用者发出通知(定期发送状态消息,或在特定事件发生时发送状态消息),或者使用某种形式的轮询机制让调用者自己负责查询状态。还有第三种可能性,我不准备在这里详细讨论了,因为它实际上并不适合企业系统;这种可能性是,发出调用的系统不需要关注其请求发生了什么结果;它仅发出请求而已,并不需要知道最后是成功了还是失败了。两种状态跟踪形式可以简单地描绘为“喂,我的定单准备好了吗?”以及“喂,您的定单已经准备好了”。它实际上就是谁先打招呼的问题。
图 8. 利用通知进行状态跟踪
一般将通知视为最有效的处理状态跟踪的方式,因为在这种方式中,只有项目状态发生更改时才发送信息;而在轮询方式中,可能会出现许多不必要的状态请求。
图 9. 使用轮询方式进行状态跟踪
但事情并非总是如此;如果检查请求状态并不是一项很频繁的操作,基于查询的状态系统可能更为有效。为了取得最大的灵活性,我建议两种方式都实施,既实施能够对状态进行请求的机制,也实施状态通知机制。我们以一个允许您在线订购产品的 Web 站点为例;您发出定单之后,通常会回到 Web 站点上查阅您定单的当前状态(只要您乐意,随时都可以查询),但该 Web 站点也可以在定单被接受、处理及发货时向您发出电子邮件消息。两种状态跟踪形式都很有用,两种方式也都对处理系统有相同的隐含要求;每一项请求的状态都需要跟踪。
异步处理的主要好处之一是,您不必等待每一步都完成;但您仍要考虑整个处理需要多长时间才能结束的问题。为了保证定单或者您正在处理的任何形式的请求最终不会让您等待过长时间才能处理完毕,需要一种方法来指定每个异步请求允许花费的最长时间。实施超时机制是防止定单在系统中耽搁好几天的唯一办法。
与上述状态跟踪问题相似,在出现错误的情况下,补偿逻辑非常重要。实际上,如果您假设每项请求都会成功,每项定单都能够成功处理,那么,系统的建立就会很省时间,设计上也会更简单。真正占用大部分设计时间和实施时间的工作是处理出现的问题——也就是处理异常情况。
补偿逻辑与数据库中的事务回滚概念相关——它在处理彻底失败时撤销任何已经完成的操作。在定单处理场合,当客户的信用卡未通过验证并因此取消定单时,补偿逻辑或许就包括撤销预留库存。在同步系统中已经提供了一些技术来处理此问题,例如数据库事务处理及 Microsoft 分布式事务处理协调器(即 MS DTC,它是 COM+ 的一部分)。根据这些事务处理技术的任一种技术,程序员可以明确声明某处理的所有步骤都是某项事务的一部分;如果出现错误,数据库或 MS DTC 提供的服务将撤销错误出现前已完成的所有工作。在异步系统中,不可能采用这些事务处理技术来管理处理中的所有步骤,因为这些步骤被不确定的时间所分割。您必须实现自己编写的代码才能撤销处理失败时已经完成的任何工作。采用多种方法可以实现这一任务,但通用方法是在运行时对处理进行跟踪/审计,然后使用跟踪信息回退并逆转每一操作。这听起来很简单,但做起来却很麻烦;开发补偿逻辑是主要的工作。
在本文其余部分,我将探讨在您自己的系统中实施异步工作流的多种不同方法,并解释每种方法如何处理上述问题。
为了解释创建异步工作流的多种不同选择,最好使用一个具体例子。对于本文其余部分,我就使用一个定单处理系统的例子,如图 10 所示,定单必须经过一个简单的四步工作流。
图 10. 由四步构成的简单工作流
并不是每一阶段发生的事情都与实施选择的讨论相关,但是我们假定可以获得一系列 .NET 组件(以及公开的 COM 接口)来处理每一步。
注意 在任何实际的系统中,可伸缩性和可靠性都是要考虑的关键问题。如果一套系统时常“丢失”定单(或者将同一份定单执行一次以上),任何这样的系统就不适合实际使用,因此,还应当谨慎设计您的软件系统和硬件系统,以保证可靠性。可伸缩性也是一个需要考虑的问题,尤其是面向公众的系统,其潜在用户量是非常大的。在本文描述的所有三种实施方法中,我将讨论在使系统能够根据需要进行扩展的同时保证可靠性的措施。
根据必须由您自己动手创建的实施工作量,我将实施工作流的选择划分为三个不同的途径:
• |
利用 Microsoft? SQL(TM) Server |
• |
利用 Microsoft .NET 和 Microsoft 消息队列 (MSMQ) |
• |
利用 Microsoft BizTalk(TM) Server |
第一种选择说明在实施解决方案时不需要依赖任何专为工作流设计的预建机制。在本例中,就是使用 SQL Server 来创建您自己的队列系统,并自己编写代码处理实际移动和处理通过定义的工作流的定单。第二种选择使用操作系统提供的 MSMQ 功能来实施定单在处理中移动时的队列编排工作,但您仍然要自己提供代码来控制定单从一个队列到另一个队列的移动,以及调用每个组件。最后一种选择是购买一套系统,即 BizTalk Server,由它来处理整个工作流过程,您自己仅使用该工具定义工作流即可。当然,在以上三种方案中,您都需要实施表达工作流中每一步发生的实际处理工作的组件。然而,值得指出的是,如果某步骤主要是转换、存储和检索各个系统中的消息,BizTalk 可以完成大部分此类任务,根本不需要编写代码。我将逐一介绍每个选择方案,并解释如何在每一实施方案中处理本定单处理的各种元素。
解决方案描述
要使用 SQL Server 构建您自己的工作流系统实施方案时,您有多种选择。您可以为工作流中的每一个状态(等待验证、已经发运,等等)创建不同的表,然后通过在表之间进行插入、删除操作将消息(例如,定单)在不同状态之间进行“移动”。这非常类似于队列系统中的工作流进行工作的方式(例如 MSMQ,将在下一实施选择方案中讨论),但让 SQL Server 从事它不擅长的工作并没有什么好处。
另一种可选模式是仅使用一张表来存储消息,并采用一个附加字段来保留当前状态。将一条消息从一个状态转变为另一个状态只涉及对该状态字段的修改,而所有的消息始终保持在原位置不动。如果要将 SQL 用做工作流引擎,我建议采用这种模式,它可以创建另一张表来跟踪定单进入处理每一步的日期和时间。
实施细节
特别符合异步业务处理概念的操作示例是在数据库表中插入新定单。在通过 Web 站点或另一个系统提交并接收定单之后,SQL Server 实施将由下面几个关键元素组成:保留定单及相关信息的表,完成每一步处理工作的组件,跟踪表的工作流,以及协调定单在此处理中移动的程序(即控制器)。由于本系统可能具有的去耦特性,该控制器不可能仅是一个程序;实际上,它由在各种不同计算机上运行的多个不同的程序组成。无论分布方式如何,控制器的概念体现了与工作流有关的全部代码;消息要在各个步骤之间移动,就需要运行控制器。使用 .NET 后,这些控制器可以按照 Microsoft Windows? 服务的方式来编写,它们不间断地运行并处理任何挂起的定单;也可以按照应用程序的方式来编写,将其设置为在特定的时间运行。在基于 SQL Server 的系统中,控制器代码可以遵照下面的基本行为模式:
For a particular step: Query database for the oldest records at this stage (SELECT TOP 1 * FROM Order WHERE Order.CurrentStage = i ORDER BY Order.Date ASC) If order exists then Process record Add entry to Tracking/Auditing table Call Component(s) to process Order If successful Update Tracking table Move Order to next stage (UPDATE Order SET Order.CurrentStage = Order.CurrentStage + 1 WHERE Order.ID = ID) If failure Compensating Logic for all previous steps (i-1 to 1) Update Tracking table
为了获得最佳的性能和最大的灵活性,可以为每一个阶段创建单独的控制器实例,也可以在每一阶段自身的线程中处理该阶段的工作;在两种方式中,控制代码都可以根据需要在不同计算机之间保持隔离。
将 SQL Server 用于工作流的主要好处是所有的东西都存储在一个数据库中。将所有的定单和它们的状态都存储在 SQL Server 之后,就有可能通过一个简单的查询来检查定单的当前状态,同时也容易通过使用相关表来实现与其他系统(这些系统可能也在使用数据库,甚至或许是相同的 SQL Server)的集成。但使用 SQL Server 也有它的缺点:主要是,它并非一部工作流引擎,而是一套数据存储系统。因此,SQL Server 对工作流系统的许多功能都没有提供支持,您必须构建将 SQL Server 用作数据存储系统的自己的工作流引擎。超时就是一个例子,它是工作流环境中必需的功能,但 SQL 不处理它。为了处理超时情形,需要一个程序(或许还要与工作流逻辑的其他部分结合在一起)来定期扫描表,以发现超过规定时间长度的记录并将它们处理为超时消息。
多线程开发
当多个处理查找同一阶段的记录时,就会遇到使用 SQL Server 的另一个问题。关于这个问题详细讨论已经超出了本文的目的,但很有必要关注这个问题,因为它极好地说明了异步及多线程应用程序所面临的问题。如果两个或多个处理试图同时处理同一阶段的工作,在使用为控制器处理提供的伪代码时就会出现问题。第一个处理将检索在某特定阶段最旧的记录,并将该记录发送给不同组件进行处理。只有在这些组件返回了更新的记录,才表明它已准备好移动到下一阶段。从检索到最终更新之间,另一个处理或许也在处理相同阶段的工作,而且它也会经历同样的步骤。在第二个处理检索该特定阶段最旧的记录时,如果第一个处理还没有进行更新操作,也没有完成其数据库的事务处理,那么第二个处理就会检索到与第一个处理一模一样的记录。此相同记录随后将再次发送给各组件,从而造成潜在的重复处理问题。有两种方法可避免此问题。第一种方法是在事务处理期间对该 Order 表使用排他表锁,只有这种方法才能阻止第二个处理在第一个处理完成之前对该表执行 SELECT 查询。这种方法能够发挥作用,但其结果是封锁了所有其他处理(甚至包括其他计算机上的处理),使得它们在此处理完成通过当前阶段的第一个定单的操作之前一直都不能访问该表,这实际上是取消了并行处理的可能性。
第二种方法是对处理稍做修改以避开此问题——仍使用排他表锁,但只限制在较短时间内。这种方法不是在对定单进行处理的全部时间内锁定表,而是在启动事务处理、进行 SELECT 操作(包括排他表锁)后即对记录进行更新,将其标记为可以继续进行其他处理。您可以使用多种方法对记录进行标记,包括设置布尔值标志或者将其更新为某个特殊的状态代码。在UPDATE 执行完毕后,您可以立即提交该事务并解锁。随后即可对该定单进行其他处理,对其他处理的封锁时间也不会大于执行 SELECT 和 UPDATE 所占用的时间。作为一个存储过程,这类似于下面的代码:
CREATE PROCEDURE GetNextOrder @Step int, @OrderID int output, @OrderDate datetime output, @CustomerID int output, @OrderStatus int output, @SubTotal money output, @Tax money output, @ShippingHandling money output, @ShipToName nvarchar(50) output, @ShipToAddressId int output AS DECLARE @NextOrder int SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION SELECT TOP 1 @NextOrder = Orders.OrderId, @CustomerID = Orders.CustomerId, @OrderDate = Orders.OrderDate, @OrderStatus = Orders.OrderStatus, @SubTotal = Orders.SubTotal, @Tax = Orders.Tax, @ShippingHandling = Orders.ShippingHandling, @ShipToName = Orders.ShipToName, @ShipToAddressID = Orders.ShipToAddressId FROM [Orders] WHERE [Orders].OrderStatus = @Step ORDER BY [Orders].OrderDate ASC UPDATE Orders SET OrderStatus = OrderStatus + 50 WHERE OrderID = @NextOrder SELECT @OrderID = @NextOrder COMMIT TRANSACTION
SQL Server 能够很快地执行此事务处理的各个步骤,因此表锁的封锁并不会对整个系统的性能造成明显损害。
注意 尽管此例使用了轮询方法来查找新定单,但也可以在 SQL Server 中使用通知模式,只是后者需要更多的“自制”代码。
可伸缩性和可靠性
使将 SQL Server 用作工作流引擎的系统具有可靠性和可伸缩性的方式与使将 SQL Server 用于其他目的的系统具有上述特性的方式是一样的。在数据库场合,处理不断增加的负载的主要办法就是“升级”,即在运行数据库的计算机中增加内存和处理器。可以将一个数据库分布到多台计算机上,以利用联盟服务器 (federated server) 和分区等功能实现分配负载的目的,从而支持极其大量的负载;但如果有 8 路CPU 或更大型的计算机,那么,通常一台计算机就足够用了。为确保可靠性,可以采用由 2-4 台计算机组成的故障转移群集;这样做的目的并不是为了提高性能,而是为了保证正常运行时间,因为它提供了多达三台服务器,它们可以在主运行服务器 (live server) 发生重大故障时接管系统的运行。关于 SQL Server 2000 系统可伸缩性和可靠性的详细信息,请参阅 SQL Server 2000 SDK 中的 Federated SQL Server 2000 Servers 以及 SQL Server 2000 Failover Clusters。
解决方案描述
.NET 框架使您能够利用消息队列 (MSMQ) 以编程方式轻松地发送和接收消息,该解决方案就以此基本功能为基础。实施这种形式的工作流解决方案需要使用许多队列——其中一个用于表达工作流的各个阶段,还需要一套用来存储最终定单的数据库表和一个审计/跟踪表。与上述 SQL Server 实施相似,本系统的关键组件也是一个控制器程序,它编写为 Windows 服务形式,设计为处理与工作流有关的处理。该程序负责从队列中接收消息,针对每条消息调用适当的组件来进行适当的处理,然后将定单发送到下一个队列。
实施细节
与上述 SQL Server 示例不同的是,本方式不通过轮询方法检查每一阶段的新定单,而是利用 MSMQ 的功能在控制器等侦听程序中激发事件。此外,还可以为每个队列创建一个线程,而且在不指定超时时间的情况下接收在每个队列上启动的处理。这两种方法之间的性能差异很小,但各自的代码差异却很大。为了获得审计与跟踪的详细信息,您还需要某种方法来存储信息,因此,很可能还需要一个数据库表。
采用 MSMQ 有很多好处,因为 MSMQ 本身提供了异步处理所需要的许多功能。在消息等待处理时,它们存储在队列中;最旧的消息自动被最先处理,因为消息队列的规则是“先进先出” (FIFO)。放进消息队列中的消息有很灵活的设置,既可以处理向队列提交消息时的超时问题,也可以处理从队列中接收消息时的超时问题。MSMQ 还内置了一些高级功能,例如将某些消息标记为高优先级或低优先级。每条消息除了其主要内容(本示例中为定单)之外还有多个属性,它们提供了对该消息的进行审计的重要信息(例如 ArrivedTime、SentTime、SourceMachine 等等)。最后,MSMQ 的编程模式在设计上考虑了异步工作流问题,它在新消息达到时能够发出通知(通过事件),从而不再需要任何轮询工作。在 SQL Server 章节中讨论的锁定与并发问题在 MSMQ 中已经不成为问题;无论有多少个处理试图同时从同一个队列中检索一条新消息,MSMQ 都可以保证不会有两个处理收到相同的消息。指定给每一阶段的进程/线程数量是完全灵活的,可以根据系统负载的变化情况进行微调。关于 MSMQ 的详细信息以及通过 .NET 对其进行编程的示例,请参阅 MSDN 上的以下两篇相关文章:
• | |
• |
虽然 MSMQ 提供了许多与工作流有关的功能,但它与 SQL Server 一样不是一部工作流引擎,您仍需要对控制逻辑编写代码,才能将消息从处理中的一个阶段转移到另一个阶段。但 MSMQ 的确为发送和接收消息提供了许多出色的功能,它们是工作流解决方案和组件的关键组件,如果在 SQL Server 基础上进行构建,必须自己对这些组件编写代码。
可伸缩性和可靠性
在本实施中使用 MSMQ 时,或者作为 BizTalk 实施中的一个组件使用 MSMQ 时,可以利用 Microsoft 群集服务对 MSMQ 进行群集设置,从而为该服务器上的队列提供故障转移支持。除了为群集提供支持外,MSMQ 还有一项引人注目的可靠性功能,即使 MSMQ 能够处理那些几乎会摧毁任何其他系统的故障。消息发送到队列时——例如从您的 Web 站点上发送到您的后端 MSMQ 服务器上 ——这是一项异步操作,即使消息尚未提交,它也会立即返回。如果网络出现故障或者服务器无法使用,就不可能抵达目的地队列,消息将自动存储在发送方计算机上(本示例中,就是 Web 服务器上),直至与目的地接通。这种机制被称为“存储及转发”(store and forward),经常用于允许移动用户脱机使用应用程序,并且还创建了一种容错能力更强的系统。在相似情形中,如果将 SQL Server 或其他数据库用作后端处理,那么必须自己将其构建到系统中才能实现“存储及转发”。
解决方案描述
最后一种您应当考虑的选择方案是使用专门设计的软件,该软件可以帮助您创建采用异步处理的系统。Microsoft BizTalk Server 正是为此而设计的软件,它不仅使您能够设计工作流系统,并且还提供一部引擎来管理这些系统的运行。选择自己编写工作流系统而不采用 BizTalk,不大可能是根据功能特点做出的决策,因为 BizTalk 可以处理几乎所有的工作流需求。相反,它肯定是根据组织的特点做出的决定,或者是根据确定软件系统操作成本的方法而做出的决定。
实施细节
BizTalk 提供了很多功能以及灵活的配置选项,很难详尽地描述 BizTalk 解决方案的各个方面,但基本概念还是很值得介绍的。作为一款特别为此目的而设计的产品,BizTalk 能够支持本文所描述的所有工作流功能,包括超时、跟踪、容错,等等。虽然已经替您处理了许多工作,有些方面仍然要您来负责。您需要利用 BizTalk Orchestration Designer 工具设计工作流过程并对其建模。
剖析业务服务
图 11. BizTalk 能够让您以图形方式对工作流过程进行布局设计 — 包括并发、事务处理边界、操作以及决策点
您还要负责实施实际定单处理中的每一步骤,尽管许多步骤都是消息转换和验证,而且它们可以通过 BizTalk 配置来处理。然而,BizTalk 支持对 COM 组件的直接调用(或者通过互操作对 .NET 调用),因此,在实际的定单处理过程中,您可以创建与上述两个实施方案相同的组件。虽然仍需要处理组件,但不需要控制逻辑了(在前述两个示例中都要使用)。BizTalk 本身处理了前述实施方案中控制逻辑方面体现的所有功能。
补偿逻辑由 BizTalk 以独特的方式进行处理:您必须创建一个专门设计的工作流,它能够逆转您的主进程的操作,当 BizTalk 检测到系统中出现故障时,它负责调用此特殊的工作流。BizTalk 的真正优势体现在其用于大型企业系统中,因为它通过群集在各组服务器之间为收缩、扩展及可靠性提供内置支持,而且它还处理多台计算机之间负载平衡工作流处理方面所需的全部工作。编写工作流系统可能是一项具有挑战性的任务,但使系统具有可伸缩性更为艰巨。BizTalk Server 已经替您完成了这项工作,这是它的一项重要优势。
可伸缩性和可靠性
本文最后讨论的工作流实施方案 BizTalk 所具备的功能使其既可伸缩又很可靠,而且所有这些对于开发人员而言本质上是透明的。如果您确定要采用两种“自己编写代码”的方案从单个服务器移到 20 台服务器的服务器站上,您很可能会遇到许多问题,需要对您的代码、甚至整个设计进行修改。但 BizTalk 是为在多台计算机上运行而设计的,因此您不必担心它会对您的个别系统带来不良影响。有关设置非常可靠的 BizTalk 实施的详细信息,请参阅文章 clustering considerations 。
我已经介绍了实施异步工作流的几种可能途径;下面,就为您的系统选择具体的实施方案,我再提出一些建议。每一种解决方案都有其优点和缺点,但是,一旦您充分考虑了具体的需求和资源,总会有一个方案脱颖而出。
开发复杂性
您需要一套采用异步工作流的系统,但是您拥有构建此系统的人员和时间吗?采用本文所述的任何一种实施方案,都需要设计及开发资源来创建前端系统(例如 Web 站点),以及对该处理的每一步骤进行处理的组件,但如果您打算采用 SQL 或 MSMQ 实施方案,只需要编写自己的工作流代码。这种代码可能最终采用多线程技术并且安装到系统中的多台服务器上,它需要高水平的开发技能才有可能正确创建出来。相应地,如果您没有自己的开发资源,或者开发人员的经验很有限,采用预建的产品或许会增加您成功完成系统的机会。即使您的开发团队胜任编写所需代码的工作,开发工作所花费的时间也是另一个要考虑的问题。任何一个系统,即使它采用 BizTalk 这样现成的产品,也需要花费大量的设计、开发、测试时间,只是我们可以期望,如果提供了大部分系统功能,这段时间会缩短一些。因此,即使您的团队能够毫无问题地构建出该系统,也必须保证能够满足它对项目具有的额外时间要求。
采用 BizTalk 并非不需要任何设计/开发成本;如果您在学习此产品用法的同时,安装并配置第一个工作流系统,您会发现这是一项十分复杂的工作。就相对简单的单个项目而言,尽管 BizTalk 可能在时间与复杂性方面比“自制”实施有优势,但 BizTalk 的初始安装和学习曲线使其优势丧失殆尽。这种初始投入能够减少后续项目所需的工作量,也能够减少对原始系统进行修改时所需要的工作量。
拥有产品所需总成本
根据组织对开发资源的成本认识不同,您对每项实施的成本也会有截然不同的计算结果。BizTalk 的许可证是按处理器数量计算的,分标准版和企业版,而且还需要您的组织拥有 Microsoft SQL Server 许可证。您可能已经拥有 SQL Server,或许它已经在您系统的另一部分使用,但您需要为您运行 SQL Server 的每一个新 CPU 购买另一个许可证(或者额外的每客户端或每客户访问授权)。根据组织当前系统状况以及待设计系统的规模不同,BizTalk Server 成本的计算也会有很大不同,但它只是您要考虑的成本之一。其他一些主要开支还有正在进行的管理成本、维护成本和支持成本。初始设计和开发过程中涉及的员工成本,以及未来对系统进行任何修改的成本,也都需要考虑。所有这些因素构成了实施系统所需要的总体成本的一部分;但当考虑本文所讨论的各种实施方案时,您会更关注各个方案在成本上的差异。例如,所有三个实施方案都可能用到 SQL Server(用于不同的任务),因此在比较过程中可以不考虑 SQL Server 的成本。同样,基础性的 Windows 2000 服务器也可以忽略,这样,在进行适当比较之前只需要确定剩下的一些关键成本。许可证、开发、支持和管理是我建议您多加考虑的主要方面,在此花费最多,所选择的实施方式对它们会有较大的影响。现在,假设目标是由每个系统为同样等级的请求提供服务,您可以开始计算各方面成本的差异了。我不打算罗列这些成本的详细数据,因为机构不同、场合不同,这些成本的变化会很大,因此,您需要在设计研究时确定适当的细节。对于所介绍的各个工作流选择方案涉及的成本详细信息,可以登录以下 Web 站点获得相关产品的价格信息:SQL Server 及 BizTalk Server。
灵活性与组织特点
最后,我还要做一些补充说明,成本不是唯一的问题,您需要考虑对您公司而言什么是最适当的。如果完全依靠自己的力量来开发您系统的大部分,而且您拥有足够的开发人员,那么可以选择“自制”解决方案,即使其成本有可能比采用 BizTalk 还要高。相反,如果您组织中 IT 部门的人员流动性很大,而您又希望运行的产品具有安全性、能够提供技术支持和后续升级,那么就应当选择 BizTalk,无需考虑成本问题。
异步工作流是很强大的体系结构,它不仅能够提高系统的可伸缩性和可靠性,也是处理自动业务处理的好方法。本文介绍了三种不同的方案,通过这些方案可以为系统添加异步处理功能,但必须选择哪种实施方案最适合您的系统。下一步涉及的是要在自制和外购之间做出选择。就自制而言,您还有两种选择。所有事情都自己解决,而不利用 MSMQ 的优势,似乎是效率最低的解决方案,但它将所有的东西都放到 SQL Server 上,这使得文档跟踪以及集成到系统的其余部分变得非常容易。采用 MSMQ 可以让您先声夺人,并且还能够带来许多构建解决方案所必需的异步功能,这样,只要根据需求编写控制逻辑以在工作流中移动消息即可。最后,即使采用 BizTalk,您仍然必须创建对各个阶段进行处理的组件,这在所有这些解决方案中都不可避免,但 BizTalk 提供了所有其他的工作流元素。
如果需要实施的是运行在一台服务器上一成不变的工作流,那么自己动手构建系统也是很实际的想法。如果您希望支持多个工作流,您的工作流变化相对频繁,或者您需要运行大型服务器组,那么需要重复完成大量的 BizTalk 功能,其成本可能会比 BizTalk 本身要高许多。一个主要的决定性因素是您对支持的要求,因为您自己的解决方案不大可能得到像 BizTalk 这样的外购产品所提供的支持水平。最后,所有这三种解决方案都是在应用程序中实施异步工作流的切实可行的方法,其差别取决于您的具体需求。