简介
在本文中,我们将说明基于Microsoft 消息队列服务 (MSMQ) 并且是 COM+ 主要功能之一的队列组件 (QC) 如何透明地提供异步和有保障的传递。我们将通过一个名为 Hanson Brothers (HB) 的示例应用程序进行说明。
具体地说,我们将通过一个 HB 贸易定单来逐步说明。该定单将从客户的观点来考虑,继而对其后台的工作流程进行深入审视。在那里,我们将集中讨论一个单独的 HB 组件,并探究它的活动和事务边界。我们将通过定单的填写过程来得出结论。
在开始之前,让我首先介绍一下 Hanson Brothers,并详细说明它的一些 COM+ 应用程序。
Hanson Brothers 是什么?
Hanson Brothers (HB) 是 Microsoft Windows 2000 Platform SDK 中附带的一个简化的零售经纪业三层 COM+ 示例应用程序。HB 支持基于 Web 和基于 Microsoft Visual Basic 的客户端。它的目的是演示真实世界中异步分布式场景 中的一系列 COM+ 服务。本文中,我们将集中讨论 HB 的队列组件元素。
注 安装 Platform SDK 后,即可在 C:/Program Files/Microsoft Platform SDK/Samples/Com/Application_Samples/Hanson_Brothers 下找到 Hanson Brothers 示例。
HB 的设计是虚构的;它并不是模拟任何现有的贸易或经纪业系统(它们通常是高度自定义的)。另外,HB 也并未尝试在所有案例中描绘“最佳做法”。HB 的设计可让用户在一个相当完整的端对端分布式应用程序中,轻松探究和体验可选路径。
这里用了四个 COM+ 应用程序来详细说明 HB 场景。
• | HBInstitution 是一家零售经纪公司。HBInstitution 负责维护客户帐户,并允许客户交易股票及订阅标题新闻和报价。 |
• | HBExchange 是一个自动化的交易和结算系统,类似于一个电子通讯网络 (ECM)。它负责自动匹配从成员机构(例如,HBInstitution)接收的 BUY 和 SELL 定单。填好的定单立即被确认返回给成员或客户的终端。 |
• | HBNews 是一种股票新闻和报价的发布服务。HBNews 可订阅来自成员交易的报价事件,以及交易中列出的公司的标题新闻。然后,HBNews 向订阅机构发布新闻和报价。本文中,我们将不讨论 HBNews。 |
• | HBNotify 是由 HBInstitution 运营的定单状态代理服务。它的目的是代表 HBInstitution 从 HBExchange 接收定单状态。为了将通知路由到正确的机构,HBNotify 使用了机构 ID。 |
如何发出定单
HBInstitution 客户通过社会保障号识别,并拥有一个或多个经纪帐户。每个帐户都维护收支平衡以及交易事务的历史记录。此外,每个帐户还维护一个所拥有或持有的股票列表,称为持有股。此外还提供一个待定定单列表。待定定单只是那些已经确认、但等待发货的历史定单。客户可以通过 UI 获得帐户详细资料、持有股和历史记录。
客户(被视为 Hanson Brothers 的基础用户)可以通过 HBInstitution 发出 BUY 或 SELL 的定单。发出的定单可以是“Market”定单 — 也就是说,立刻进行买卖;也可以是“Limit”定单 — 也就是说,等待直至符合需要的买卖价格。要发出定单,客户应该单击一个标记为“Verify”的确定执行按钮。如果客户现有的资金足够以当前的市场价购买股票,或现存足够的股票出售,客户便可以“确认”或发出定单。
客户可以通过定期检查历史记录来跟踪定单的状态。例如,在成功执行后,历史记录随即将定单状态显示为 Institution-Confirmed (I_CONFIRM)。不久以后,当 HBExchange 收到定单时,定单的状态将变为 Market-Confirmed (M_CONFIRM)。再经过一段时间,HBExchange 将根据定单的类型和价格填充定单,定单的状态将设置为 Settled (SETTLED)。
结算被定义为交易 + 零营业日。因此,对于 HB 而言,定单填充 和定单结算 这两个术语具有相同的意义。
在定单能够被认为是拥有或持有之前,将转换到的最后一个状态是 Reconciled (RECONCILED)。在 HBInstitution 协调所有已结算定单与它们各自的帐户后,即可达到这一状态。这通常是在每个交易日结束之前执行的批操作。
使用 QC 的异步定单工作流
当客户发出定单后,系统将使用许多独立运行的“逻辑”线程或活动来完成定单请求。以下每一个活动都可以与其他客户定单活动并行执行。
• | 客户发出定单。 |
• | 机构验证定单,将定单存储到持有股表中,排列定单以等待交易,并通知客户定单接收 (I_CONFIRM)。 |
• | 交易所收到定单,将定单存储到 pendingTrades 表中,通知机构定单接收 (M_CONFIRM),并将定单提交到市场交易区。 |
• | 市场收到定单,从 pendingTrades 表中读取定单,填充定单,并通知机构定单结算 (SETTLED)。 |
• | Notify 从交易所收到通知,并指示机构使用 M_CONFIRM 或 SETTLED 状态来更新持有股表中的定单记录。 |
当这些活动结束后,定单被视为已完成。
对于本文而言,当定单结算后就认为定单已完成,而不是在随后的协调之后才算完成。
HB 系统的坚固性由服务器对象的事务属性,以及在这些活动中执行的服务器对象的调用类型来决定。图 1 展示了一个典型的定单流程,并标识了流程中的这些活动、它们的任务以及它们的事务特性。
图 1. 异步定单工作流程
第一步:客户 Jon 向 HBInstitution 发出了一个买一份 Walleye Pond Bakery (WEYE) 的“Buy at the market”订单。该定单被同步提交到 HBInstitution.Iorder。
If Request("submit") = "Execute" then rc = objOrder.Execute(ssn, AcctNum, symbol, shares, price, TradeAction, _ TradeType, iOrderNum, true, False, 0, 0, sMsg)
第二步:HBInstitution.IOrder.Execute接收并验证 Jon 的定单。对 Jon 执行交易的授权进行声明并通过程序验证。例如,方法级安全可确保 Jon 是 Trader、Rep 或 Customer 角色的成员。安全上下文用于确定 Jon 并未(例如)从 Web 上使用交易登录帐户来发出定单。
同时,还要检查 Jon 的帐户以确保有足够的资金来购买一份 WEYE。当前的 WEYE 报价用于估算费用。
第三步:Institutional DB 被更新。Jon 的帐户现金收入在帐户表中被更新,并且定单通过一个存储过程插入持有股表中。这一过程将返回一个在确认信息 (sMsg) 以及后面的处理中会用到的唯一定单 ID (iOrderNum)。Jon 定单的初始状态是 Institution Confirmed (I_CONFIRM)。
第四步:将定单排入队列以等候交易。HBInstitution.IOrder.Exexcute 会创建一个队列 HBExchange 对象,并调用一个方法来异步传递 Jon 的定单信息。
Set oExchangeOrder = GetObject("queue:/new:HBExchange.IExchangeOrder")
使用队列名字对象创建 HBExchange.IExchangeOrder 时,HBInstitution.IOrder 接收到的并不是一个直接的对象引用,而是一个对调用记录器的引用。当调用方法时,系统将在客户端记录调用。当客户端释放组件时,QC 使用 MSMQ 将记录的调用传送到服务器。
QC 记录器用队列名字对象中提供的信息以及 COM+ 目录中的信息对自身进行初始化。通常,直到记录器的最后一个引用被释放并且仅当方法被调用时,QC 记录器才会发出 MSMQ API 调用。
服务器侦听消息并激活一个创建实际 IExchange 对象的 Player 组件,然后将记录的调用置于其中。站在客户端 (HBInstitution) 的观点看,在使用队列组件和非队列组件之间,唯一的不同在于组件被激活的方式。
Call oExchangeOrder.EnterOrder(sSSN, sAcctNum, sSymbol, iAction, iQuantity, cLimitPrice, iTradeType, lOrderId, iInstitutionId, bQueued, bTrackFlow)
参数 bQueued 用于指示处理是通过 QC(默认)来异步操作,还是同步操作。参数 bTrackFlow 用于决定是否开启定单跟踪。参数 iInstitutionID 用于告知 HBNotify 哪个机构应接收传入的 HBExchange 定单通知。
HB 能够通过传递队列回调通知引用而不是一个整数,来实现一个响应对象。通过传递一个响应对象,HBNotify 对象的标识得以从 HBExchange 之手移至 HBInstitution。这样就能够,例如,以不同于处理或指示客户定单的方式,来处理或指示共有基金管理定单的通知。
在封送记录器的过程中(当您将记录器的引用作为接口指针传递时),QC 记录器并不参照队列管理器来封送队列格式名。如果响应对象记录器的创建、封送和释放均不使用方法调用,那么将没有对 MSMQ 或网络的引用。但是,如果在队列名字对象中提供了重写参数以改变队列名,则这些参数将在记录器的创建过程中进行验证,并且队列也将在那时被打开。
第五步:IOrder.Execute 将以下消息返回给 Jon:
“Order Entered Successfully.Your Status (Institutional) Number is 946”
Jon 通过此消息来确定他的定单被正确接收。如果 Jon 查看他的定单历史记录,就会注意到定单 946 的状态为 I_CONFIRM’ed。
为了保证定单的全部处理或全部不处理,这些任务被置于事务控制之下。在 Visual Basic IOrder.cls 中选择“Requires New Transaction”属性,或者通过组件管理器,可以将该方法定义为根定单事务。如果 IOrder 失败了,所有的工作都将被撤消或回滚(包括队列调用),并且 Jon 将收到一个相应的错误消息。
第六步:Player 创建服务器端组件,并运行
HBExchange.IExchangeOrder.EnterOrder。
第七步:定单被插入到交易的 pendingTrades 表中。
On Error GoTo ErrorHandler 脗聟 cmd.CommandText = "sp_HBInsertPendingTrades" cmd.CommandType = adCmdStoredProc cmd.Parameters.Append cmd.CreateParameter("ssn", adVarChar, adParamInput, 10, sSSN) 脗聟 rs.Open cmd lConfirmNumber = CLng(rs("confirmationNumber")) ' Unique exchange number rs.ActiveConnection = Nothing 脗聟
第八步:系统使用队列组件 HBNotify,将一个“market”确认消息 (M_CONFIRM) 发送至 HBInstitution。此通知是 HBExchange 对 HBInstitution 的担保:Jon 的定单已可靠地存储起来。
Set oNotify = GetObject("queue:/new:HBNotify.INotify") Call oNotify.ProcessConfirmation(lOrderId, iInstitutionId, lConfirmNumber, bTrackFlow)
当 INotify 返回一个 market(或 settled)确认调用时,它使用 HBInstitution.IOrder 产生的机构 ID 来调用正确的 HBInstitution 服务器组件。参数 IOrderID 用于在持有股表中更新 Jon 的定单记录。通过排队这些调用,INotify 能够成为一个轻量中介,从而可能根据定单 ID 实现简单形式的自定义负载平衡。
第九步:完成对确认调用的记录后,EnterOrder 通过读取当前的报价并将定单提交到 IMarketFloor 组件(在其中检查定单的确定结算),来完成任务。
Set oQuote = New HBExchange.IQuote rc = oQuote.RetrieveEx(strSymbol, dQuoteTime, cBidPrice, 脗聟) Set oMarketFloor = New HBExchange.IMarketFloor ' Default, used to illustrate
Visual Basic 中 New 的实现取决于对象在哪里被创建。如果对象是在同一个项目中被创建,则不需要使用 COM 激活。如果是在不同的项目中被创建,则需要使用 CoCreateInstance。
Call oMarketFloor.GetMatchingTrades(lOrderId, strSymbol, cBidPrice, 脗聟) If Not GetObjectContext Is Nothing Then GetObjectContext.SetComplete Exit Sub ErrorHandler: 脗聟 If Not GetObjectContext Is Nothing Then GetObjectContext.SetAbort LogError "IExchangeOrder.EnterOrder." End Sub
用 New 关键字激活 IMarketFloor 将满足 HB 要求 market(以及 limit)定单被立即处理的需求。但是,同步执行是应采用的正确方法吗?
关键路径和时间无关性
回答这一问题的办法之一是查看 EnterOrder 的任务,并根据关键路径和时间依赖项来关注它们。如果不存在依赖项,那么这一同步的任务集就可以分割为两个独立的行为。
可以根据 IExchangeOrder 是否应等待 ImarketFloor,以及来自 IMarketFloor 的失败是否对 IExchangeOrder 的处理有影响,来判定 IMarketFloor 是否在 IExchangeOrder 的关键路径中。如果回答是否定的,那么与 IMarketFloor 间存在的是一种“发送后即不再管理”的关系,且 IMarketFloor 被视为在 IExchangeOrder 的关键路径之外。
HB 对定单接收和 (market) 定单实行的响应时间使用不同的规则。例如,HBExchange 与 HBInstitution 的服务质量协议可能显示定单接收(至 INotify 队列)用了两秒的响应时间,而剩余的 (market) 定单填充时间完全取决于市场条件。这意味着在这两个组件之间存在一种时间独立的关系。
由于 IMarketFloor 与 IExchangeOrder 间没有共用依赖项,因而可以使用两个单独的活动。这能够很容易地实现,方法是将:
Set oMarketFloor = New HBExchange.IMarketFloor
改为:
Set oMarketFloor = GetObject("queue:/new:HBExchange.IMarketFloor")
用二进制兼容性重做 HBExchange.dll,以及在 COM+ Application IMarketFloor 接口中设置队列属性。
注:
• | HB 对于二者均提供实现。 |
• | HBExchange.IUpdateQuote.Update() 可能也会将 IMarketFloor 排队。 |
• | 使用队列组件需要满足一些必要条件。队列组件要求所有的应用程序方法仅包含参数,没有返回值。(请参见 HBExchange.IMarketFloor 的 Microsoft Visual C++ 和 Visual J++ 版。) |
将 IMarketFloor 分为单独的异步活动也有助于依大小排列交易,而不管 IMarketFloor 是否使用了事务。例如,市场活动的突然增长将增加 IExchangeOrder 的负载,而不会立刻影响结算过程。随着时间的推移,这一负载将转移至 IMarketFloor。此外,IMarketFloor 上的负载可能加大,但这与新定单无关。例如,在市场崩溃期间加载大量免损限度限制的定单时,就可能发生这种情况。因此,每个组件将发挥它们的最大能力进行工作,且彼此不相关。
事务边界
现在,EnterOrder 处理已分解为两个活动,它应该如何作为事务处理,又如何响应失败呢?
IExchangeOrder 的事务行为取决于它的事务支持级别,以及异常是由 IExchangeOrder 还是由 player 捕获和处理。下表使用一个非事务处理 IMarketFloor 来探究这些事务的注意事项。
IexchangeOrder事务 | 经处理的 SetAbort | 经处理的 SetAbort |
无 | 定单可能丢失* | 定单可能被输入多次 |
支持 | 定单重试 | 定单重试 |
需要新建 | 定单可能丢失* | 定单重试 |
* Player 没有接收到失败的 HRESULT 或异常,也没有参与一个 IExchangeOrder 中止。因此,定单消息已成功出列。EnterOrder 必须正确地 处理异常,否则定单可能会丢失。
如果非事务处理 IExchangeOrder 处理程序选择不引发异常,这样做定单将不会被重放。处理程序如何响应取决于错误的种类。对于一个 DB 错误,处理程序可能会启动一个补偿事务,或亲自解决这一 DB 问题。补偿事务将会删除持有股记录,要求机构通知客户:他之前被确认的定单不再有效。由于丢失定单会对业务相当不利,因此处理程序更明智的做法是将定单重新提交给一个可用的 DB 服务器。如果在排队通知时发生错误,处理程序可以选择忽略这一问题,并发送一个警告消息。这一问题不会立刻被视为很严重,因为 IMarketFloor 依然能够在 pendingOrders 表中找到定单。
在一个新事务下发生同样的问题时,不引发异常。尽管现在 IExchangeOrders 的工作能够作为一个整体回滚,但仍然没有办法来重放出列的定单消息。
从一个非事务处理 IExchangeOrder 中引发错误会导致不需要的重复。定单会被多 次插入 pendingTrades 表、确认并结算。若组件失败且工作未回滚,player 将收到一个失败的 HRESULT 或捕获一个异常,定单消息随后会重放,此时就会出现重复定单。例如,当一个 market 定单消息最初被处理时,EnterOrder 会产生 2 个通知(一个是 M_CONFIRM,一个是 SETTLED — 由 IMarketFloor 产生)。当 player 捕获到一个 EnterOrder 异常时,定单消息将失败,并且 QC 会将定单移至第一个专用的重试队列 (hbexchange_0)。从这一队列进行的三次重放努力将导致产生八个队列通知。如果继续失败,定单消息将移至下一个重试队列,并在这里再做三次尝试。这将导致产生 14 个队列消息。到第四次 (hbexchange_4),将产生 26 个通知 — 也就是(4 个重试队列 * 3 次尝试/每队列 * 2 个通知)+ 2。这样的话,大约 30 分钟后,会有 13 个结算的定单被插入 PendingTable 中,而不是我们所期望的一个。
设置 IExchangeOrder 支持事务将允许 QC 参与定单的中止与重试。如果 IExchangeOrder 在事务的中间失败了,消息不会在 hbexchange 队列中丢失。该消息将连同所有 EnterOrder 工作一起被回滚或撤消。如果问题持续得太久,消息可能会位于一个最终静止队列中,在那里可能会需要进行手工干预。
特别是,当最后一个重试队列 (hbexchange_4) 上的最后一次重试失败,队列组件运行库将从消息中检索目标组件,并检查 HBExchange.IOrderExchange 中定义的异常类。如果已定义异常类,运行库将对该异常类进行实例化,查询 IplaybackControl,并调用 IPlaybackControl::FinalServerRetry。然后,运行库执行 EnterOrder 方法,从消息向一个其执行可能涉及将定单提交到不同 DB 服务器的异常类。如果异常类未定义,或者它的激活失败,消息将移至最终静止队列 (hbexchange_deadqueue),并停留在那里直至被手工移除。
在一个新事务下引发异常将启动同样的重试。中止将回滚方法上执行的所有工作,处理程序引发的异常将指示 Player 重新排队。
为了更详细地探究 QC 的事务特性,可以从客户端和服务器端的观点来考虑 QC。
客户端边界
从客户端 (HBInstitution.IOrder) 出发,记录器假定它代理的服务器端对象 (HBExchange.IExchangeOrder) 的事务特性。如果服务器组件的客户端目录表示形式被作为事务处理,那么记录器就是一个事务处理组件。相反,如果服务器端组件不使用或不需要事务,记录器就不是一个事务处理组件。例如,如果服务器端组件“需要新事务”,那么客户端表示形式将把记录器放入一个新事务中。如果记录器中有一个事务并且 MSMQ 队列被作为事务处理,那么 MSMQ MQSend 操作会被包含在记录器的范围之中。如果 MSMQ 队列不是作为事务处理,消息将被发送并且客户端事务可能随后中止。如果队列被作为事务处理而记录器并未作为事务处理(正如 HB 当前实现的那样),将使用一个具有“MQ_SINGLE_MESSAGE”事务值的 MQSendMessage,用以告诉队列管理器为事务处理队列接受此消息,但并不涉及当前 (DTC) 事务中的 MQSendMessage 操作。QC 使 MQSendMessage 或 MQReceiveMessage API 上的参数与队列属性以及您是否在事务中的组合相一致。(有关 MQ_NO_TRANSACTION、MQ_MTS_TRANSACTION 以及 MQ_SINGLE_MESSAGE pTransaction 参数,请参阅 MSDN 文档。)QC 还可确保当前 DTC 事务的 ITransaction 指针在适当的时候使用。当记录器被释放时(即最后一个接口引用被释放),它将向 MSMQ 发送复合消息。客户端的事务边界包括由记录器组件产生的所有 MSMQ 发送,再加上由客户端组件所作的所有资源管理器更新(通常是 SQL Server 数据库更新)。该范围不包括服务器端的任何东西。事务以及存放在 MSMQ 队列的客户端表示形式中的消息,均在客户端完成。是否连接到服务器将无关紧要。
在客户端事务提交后,MSMQ 将消息从客户端移至队列所在的服务器。如果队列被作为事务处理,MSMQ 将把这作为一个单独的事务在 MSMQ 中完成。该事务确保消息仅被传递一次,并且在客户端被删除,并在服务器端原子性安装。DTC、QC 以及您的应用程序与该事务无关;该事务是 MSMQ 的内部事务。
服务器端边界
当 MSMQ 将消息送至服务器后,它将唤醒 QC 侦听程序。QC 侦听程序会创建一个称为“Integrator”的事务处理组件。在 Integrator 的事务处理上下文范围内,将发出一个 MQReceiveMessage 操作。如果队列被作为事务处理,MSMQ 出列将加入服务器端事务。当成功出列后,Integrator 会创建一个 Player(一个不影响事务的内部组件),然后该 Player 将创建服务器端组件。如果该组件被标记为“Supports”或“Requires”事务,那么它及其它涉及的资源将加入 MSMQ 出列内发出的事务。如果该组件被标记为“Requires New Transaction”,那么将创建另一个能够带有独立输出的事务。通常,服务器端组件将被标识为“Supports”或“Requires”。
如果创建的 MSMQ 队列不具有事务属性(开发人员能够在创建 QC 应用程序之前手工做的一些事情),Integrator 这样一个事务处理组件仍然能够打开并读取队列,但是队列并不参与到事务中。这样,一个消息可以出列,启动应用程序,然后断电,在重启后,输入的消息尚未得到成功处理就消失了。基于这个原因,QC 创建的默认队列是事务性的。
IExchangeOrder 的实现
加在该组件上的最高约束是,它要具有很高的性能,并且考虑到市场情况,定单要能立即被存储。在发生故障(例如 DB 服务器崩溃)的情况下,由于故障在一个合理的时间内也许能够排除、也许不能够排除,因而会延迟市场定单的接收,所以 HB 不能等待。这一设计可以确保 HB 能够主动而迅速地排除故障。
较少关注的是不太可能发生的通知故障。如果客户端 MSMQ 发生了持久性故障,您可以通过操作干预并手工纠正这种状况。由于客户仍然可以进行机构确认,并且他填充定单的功能也没有受到损害(因为定单已成功输入 PendingTrades 中),因此让(修改后的)通知用几个小时到达 HBInstitution 还是合理的。
简言之,为了提高性能以及降低让定单因为长时间故障而被延迟的风险,系统不会将 IExchangeOrder 作为事务来处理。
注这种实现反映了 HB 场景的特定性而不是那些一般规则。此外,HB 当前没有使用群集或实现 DB 的故障切换功能。
由于可能会有重复问题跟随着通知的失败而发生,因此引发异常也不在考虑之中。
让我们跳过 IexchangeOrder 返回到 Jon 的定单。
第十步:如果 HBNotify 组件正在运行,QC 侦听程序将把 Jon 的市场确认消息从 hbnotify 公共队列中拉出,并指示 Integrator/Player 创建和运行 ProcessConfirmation。该方法可创建一个 HBInstitution.IHoldingUpdate 的非队列实例,并调用 ConfirmOrder。
第十一步:ConfirmOrder 通过经市场确认的状态来更新 Jon 的持有股记录(在第三步中创建)。这样,如果 Jon 选择刷新他的客户端,将发现他的定单状态已变为 M_CONFIRM。
在最后的步骤中,IMarketFloor 将填充(结算)Jon 的定单。
IMarketFloor 的实现
当定单经过市场确认后,IExchangeOrder.EnterOrder() 将调用 IMarketFloor.GetMatchingTrades() 来尝试填充定单(请参见第九步)。这一方法负责用当前的市场状况(也就是说,由一个称为 HBFeed.exe 的 HB 应用程序生成的特定股票报价)来匹配位于 pendingTrades DB 表中的 market 或 limit 定单。例如,Jon 的 market 定单将被立即填充,而不管当前 WEYE 的价格如何。但是,如果 Jon 的定单是一个要以 $53 的限定价格购买一份股票的请求,那么直到 WEYE 的价格为 $53 或者更低的时候,Jon 的定单才会被结算。
第十二步:方法 GetMatchingTrades 通过首先执行一个多步的存储过程(其中的第一个操作就是将所有匹配定单的状态从 PENDING 更新为 UPDATING)来填充定单。该存储过程的第二步将把第一步中更改的那些定单作为一个记录集返回。
第十三步: 然后,GetMatchingTrades 在记录集中进行迭代,并通过在队列组件 HBNotify 上调用 ProcessSettlement 方法来为每个定单发送结算通知。当每个通知都被调用(被记录)之后,将使用一个 SQL 连接字符串把定单状态从 UPDATING 更新为 SETTLED。
当所有的通知都发出后,系统将执行 SQL 的连接字符串,并且所有的定单都被更新为它们最终的结算状态。如果在这一过程中出现了错误,过程可以继续发送下一个通知(继续执行下一个),但定单已被标记为 UPDATED,而不是 SETTLED。由于 IMarketFloor 处理所有错误并且未作为事务处理,因此不管组件是否故障,所有记录的通知都将发送。
因为这种更新-读-更新的工作方式很适用于转换死锁,所以 IMarketFloor 不是作为事务处理的。当两个并发事务都持有一个共享锁(读),以至于二者都不能转换为排他锁(要求更新)时,就会发生这种类型的死锁情况。当这种死锁情况发生时,SQL 将自动中止它的一个连接,并可能返回一个 1205 错误。一般的预防措施(即在存储过程 SELECT 中使用 UPDLOCK 提示)被证实是无效的,因为读记录将超越批操作而存在。
IMarketFloor 必须实现一种不使用事务且相当可靠的设计。HB 不能只是将匹配定单设置为 SETTLED,因为迭代过程中的失败可能会让记录处于一种可疑状态(也就是说,如果通知不能被发出,定单能够被视为已结算吗?如果答案是否定的,那么在通知过程中遇到一个错误时,定单必须被设置为 non-SETTLED 状态)。为了避免将一个不能发送的记录设置为 settled,GetMatchingTrades 会连接一个 SQL 字符串作为被发送的通知。(我们提供了一个记录集批更新工具作为一种替代方案。)
注这里遇到的问题是为进行广泛的更新而人为制造的市场设计问题。更实际的实现是让 HBExchange 为每一支股票创建单独的 IMarketFloor 实例。每个实例将处理单独的 BUY 和 SELL 队列中的待定定单。
第十四步:最后,运行 ProcessConfirmation 并将持有股 DB 表中的定单更新为 SETTLED 状态。
小结
许多现实生活中的应用程序会兼顾适当的同步 COM 调用以及可能的使用 QC、LCE 或 MSMQ 的异步/延迟活动。对于需要可靠消息传递的异步活动,直接使用 QC 或 MSMQ 的决定可能取决于需要多高的性能、在消息传递上需要多大的控制、在回复之前需要多大的灵活性,以及开发人员有多熟练。
对于那些要使用 COM 程序设计模型来简化异步解决方案实施的人来说,不必担心底层管道的复杂性,队列组件是值得考虑的一个方法。
posted on 2007-06-09 01:33 子原 阅读(9) 评论(0) 编辑 收藏 引用 网摘 所属分类: VB