处理事务
事务性接收器
事务处理接收方和非事务接收方之间的main区别在于,事务接收方创建并使用显式 MSDTC 事务来确保其数据源与BizTalk Server MessageBox 数据库之间的原子性。 通常,适配器的其他方面均相同。
应该注意的是,请求-响应接收适配器仅使用某一事务来将原始请求消息提交给消息引擎。 为将从消息引擎发送的响应传输到适配器,需要不同的事务。 其原因在于,第一个事务的作用域在适配器和 MessageBox 数据库之间。 在原始请求消息的事务提交前,后续的请求消息不会从消息引擎发送到适配器。
下面的对象交互图描述了在传入消息的事务性提交过程中适配器和消息引擎之间的交互。 在该示例中,将按照以下顺序进行交互:
适配器从引擎中获取新批。
适配器创建新的 MSDTC 事务。
适配器从其已在事务中登记的数据源进行破坏性读取。
适配器提交消息。
适配器在批处理上调用 Done ,并传入其 MSDTC 事务和 BatchComplete 回调指针。 引擎返回 IBTDTCCommitConfirm 接口。
引擎处理批处理,对其 BatchComplete 实现重新调用适配器,并将其消息处理状态传达给适配器。
如果批处理成功,适配器将提交事务,并使用表示提交的值调用 IBTDTCCommitConfirm.DTCCommitConfirm API
true
。
事务性发送器
事务性适配器在很大程度上与非事务性适配器十分相似。 主要差别在于,事务性适配器将消息中的数据发送到已在 MSDTC 事务中登记的资源。
实现提示: 对于事务发送,适配器应使用相同的 MSDTC 事务将数据写入目标并通过 IBTTransportBatch.DeleteMessage 方法调用将其删除。 只有这两个操作需要进行事务处理。 任何其他操作(如 IBTTransportBatch.Resubmit、 IBTTransportBatch.MoveToNextTransport 和 IBTTransportBatch.MoveToSuspendQ )都不需要交易。 原因在于:引擎隐式使用事务,并且就目标而言,这些类型的操作无需是原子的。
下面的对象交互图说明了适配器和引擎之间的交互。 事件的顺序如下:
引擎从适配器获取新批。
引擎将两个消息添加到这个新批。
引擎对批处理调用 Done ,导致适配器将批处理发布到其线程池提供服务的内部传输队列。
适配器创建新的 MSDTC 事务。
适配器传输在 MSDTC 事务中登记目标的消息。 例如,这可能写入SQL Server数据库。
在传输后,适配器从引擎获取新批。
适配器为已成功传输的消息调用 DeleteMessage 。
适配器在批处理中传入其 DTC 事务时调用 Done 。 引擎返回 IBTDTCCommitConfirm 接口。
引擎处理该批,并且从应用程序队列删除消息。
引擎回调适配器的 IBTBatchCallback 接口,并返回有关其删除操作成功的信息。
如果该批成功,适配器将提交事务。
适配器调用 IBTDTCCommitConfirm.DTCCommitConfirm 来通知引擎事务已成功提交。
事务性要求-响应适配器
与双向接收不同,可以通过使用相同的 DTC 事务执行双向发送。 事务处理请求-响应适配器应对 SubmitResponseMessage 和 DeleteMessage 操作使用相同的 IBTTransportBatch。 此批应使用用于收发要求-响应消息对的相同 MSDTC 事务。 这确保要求-响应消息交换的原子性。
服务组件和 BYOT
消息引擎 API 要求提供 MSDTC 事务。 但是,某些 .NET 组件设计为用于服务组件,不允许以编程方式提交或中止事务。 事务而是由该平台上的 COM+ 运行库自动提交。
对于这些情况,适配器应使用 BYOT(Bring Your Own Transaction,生成您自己的事务)。 这允许适配器创建 MSDTC 事务,实例化使用该事务的 .NET 组件,以及允许该组件继承已创建的事务,而非创建它自己的事务。 .NET Framework为此提供 System.EnterpriseServices.BYOT。 SDK BaseAdapter 为此提供了一个帮助程序类 BYOTTransaction。
避免争用情况
编写创建事务对象的适配器并将其交给BizTalk Server时,需要负责编写执行以下操作的代码:
解决消息中与批相关联的错误。
确定与批操作相关联的事务的最终结果。
适配器必须通知BizTalk Server事务的最终结果,以维护其内部跟踪数据。 适配器通过调用 DTCConfirmCommit 通知BizTalk Server结果。 如果适配器没有这样做,则会发生重大内存泄漏。
上述两个任务(解决错误和确定最终结果)看似非常简单,但实际上,它们依赖于来自不同线程的信息:
适配器根据BizTalk Server传递给适配器中的 BatchComplete 回调的信息处理错误。 此回调针对适配器的线程。
DTCConfirmCommit 是 IBTDTCCommitConfirm 对象上的方法。 IBTDTCCommitConfirm 对象的实例由批处理 IBTTransportBatch::D一次调用返回。 此实例与 IBTTransportBatch::D调用 位于同一线程上,这与适配器的线程不同。
对于适配器对 IBTTransportBatch::D 每次调用时,都有相应的回调 BatchComplete,消息引擎在单独的线程中调用该回调来报告批处理提交的结果。 在 BatchComplete 中,适配器需要根据批处理是通过还是失败提交或回滚事务。 在任一情况下,适配器都应调用 DTCConfirmCommit 向消息引擎报告事务的状态。
可能存在争用条件,因为适配器的 BatchComplete 实现可以假定执行 BatchComplete 时,IBTTransportBatch::D one 返回的 IBTDTCCommitConfirm 对象始终可用。 但是,即使在 IBTTransportBatch::D one 返回之前,也可在单独的消息引擎线程中调用 BatchComplete。 当适配器尝试作为 BatchComplete 实现的一部分访问 IBTDTCCommitConfirm 对象时,可能会发生访问冲突。
在以下示例中,通过使用事件来解决该问题。 在这里,通过使用事件的属性来访问接口指针。 get 总是等待 set。
protected IBTDTCCommitConfirm CommitConfirm
{
set
{
this.commitConfirm = value;
this.commitConfirmEvent.Set();
}
get
{
this.commitConfirmEvent.WaitOne();
return this.commitConfirm;
}
}
protected IBTDTCCommitConfirm commitConfirm = null;
private ManualResetEvent commitConfirmEvent = new ManualResetEvent(false);
现在,将 IBTTransportBatch::D one 中的返回值分配给此属性,并在 BatchComplete 调用中使用它。