本页内容
概述
BizTalk Server 2004
为生成基于消息和面向业务过程的应用程序提供一个可伸缩、可管理的平台。通过这一最新版本,BizTalk
Server
在体系结构、设计、性能、可管理性和可伸缩性方面提供很多改进。为了充分利用该平台,很多架构师和开发人员需要更多有关该平台的工作方式的详尽知识。本文旨在提供有关
BizTalk Server
的消息处理基础结构,以及有关该产品的其他关键领域(客户通常会尽可能地去了解这些内容)的详细信息。
提供该信息的目的是这将帮助客户设计、创建和调试应用程序,以便他们更好地利用
BizTalk Server 2004 平台。
BizTalk Server 体系结构
在考察有关 BizTalk Server
中的各种消息处理组件的更多详细信息之前,重点要了解这些组件是如何与该产品的总体体系结构相适应。BizTalk
Server 建立在发布/订阅
体系结构之上,在该体系结构中,消息发布到系统中,然后由一个或多个活动的订户接收。该体系结构有不同的模型,但在
BizTalk Server 中实现的模型被称为基于内容的发布/订阅。
在基于内容的发布/订阅模型中,订户使用一组有关消息的准则来指定他们希望接收的消息。消息在发布的时候受到评估,并且所有具有匹配订阅(由筛选表达式指示)的活动订户都会接收该消息。但是,就
BizTalk Server
而言,基于内容有一点用词不当,因为用于生成订阅的准则不是必须来自消息内容,并且还可能包含有关消息的上下文信息。本文档中的“BizTalk
Server 中的订阅”主题讨论订阅机制的详细信息。
下图从消息处理角度提供 BizTalk Server
体系结构的高级概述。
图 1 BizTalk Server 2004
体系结构
在这一经过简化的视图中,消息被通过在给定接收端口中定义的接收位置进行接收。该消息被该接收位置处理,然后被发布到数据库
— BizTalk Server 的主要暂留和路由机制。MessageBox
评估活动订阅,并且将该消息路由到那些带有匹配订阅的编排和发送端口。下列各部分描述上述各个组件。
接收端口和接收位置
接收端口是一个或多个定义 BizTalk
Server 的特定入口点的接收位置的集合。接收位置
是接收适配器和接收管道的组合。适配器
负责消息接收的传输和通信部分。这方面的示例包括
File 适配器和 SOAP
适配器,两者中的每一个都从不同类型的来源接收消息。接收管道负责准备消息以便发布到
MessageBox 中。管道
是按顺序执行的一系列组件,每一个组件都向消息提供特定的处理,如解密/加密、分析或验证。
发送端口和发送端口组
发送端口组是一个发送端口集合,其工作方式非常类似于电子邮件通讯组。发送到发送端口组的消息将被发送到该组中的所有发送端口。发送端口
是发送管道与发送适配器的组合。发送管道负责准备来自
BizTalk Server
的消息以便将其传输到另一个服务。发送适配器负责通过特定的协议(如
SOAP 或 FTP)实际发送该消息。
MessageBox 数据库
BizTalk Server 中的发布/订阅引擎的核心是
MessageBox 数据库。MessageBox
由两个组件组成:一个或多个 Microsoft® SQL
Server 数据库和 Messaging Agent。SQL Server
数据库为很多内容(包括消息、消息部分、消息属性、订阅、编排状态、跟踪数据、路由宿主队列和其他内容)提供永久存储。
此外,通过存储过程,该数据库提供一些与路由消息和完成订阅有关的业务逻辑。但是,Message
Agent
是封装和抽象数据库组件的组件,并且是由
BizTalk Server
的所有部分用来与消息处理子系统进行交互的接口。Message
Agent 是一个组件对象模型 (COM)
组件,它为发布消息、订阅消息、检索消息等提供接口。该接口是其他
BizTalk Server
组件(包括适配器框架和编排)用来与 MessageBox
交互的唯一机制。
Endpoint Manager
Endpoint Manager(图中未显示)负责管理发送和接收端口,并充当这些端口和
MessageBox 之间的中间方服务。
宿主和宿主实例
宿主
是一种应用程序的逻辑表示,该应用程序将承载提供
BizTalk Server 中特定功能的服务。宿主可以是进程内宿主
— 即它由 BizTalk Server 拥有并管理,或者是独立宿主
— 即 BizTalk Server 代码在不是由 BizTalk Server
控制的进程中运行。独立宿主的恰当示例是
Internet 信息服务 (IIS),它承载 HTTP 和 SOAP
适配器的接收功能。宿主是为整个 BizTalk
Server 组(共享配置、MessageBox 和端口等的
Server 的集合)进行定义。
每个宿主都可以有零个或一个运行于 BizTalk
Server
组中各个服务器上的宿主实例。对于进程内宿主,每个宿主实例都是一个
Windows NT 服务(BTSNtSvc.exe)。每当通过 BizTalk
Server
管理控制台添加新的宿主实例并用启动参数(该参数指定实例表示的宿主的名称)对它进行配置时,都会安装该服务。对于独立宿主,每个实例都表示进程(该进程包含与
BizTalk Server
有关的处理代码)的一个运行实例,例如 IIS
中的应用程序池。
在上述每个宿主实例中,有多个子服务在运行。在
BizTalk Server Management 数据库的
adm_HostInstance_SubServices
表中可找到确切的服务。下面列出这些服务,以供参考:
Caching
|
由 BizTalk Server
在内部使用以缓存其他服务的配置信息。
|
Endpoint Manager
|
负责承载服务和发送端口,包括适配器和管道
|
Tracking
|
负责根据情况将数据从
MessageBox 数据库移动到 BAM 或 Tracking
数据库
|
XLANG/s
|
BizTalk Server
编排的宿主引擎
|
MSMQT
|
MSMQT 适配器服务;在与
BizTalk Server 交互时充当 MSMQ
协议的替换协议
|
Windows NT服务仅充当容器,以承载上述其他服务。当该服务启动时,上述各个子服务也随之启动。它们完成与
BizTalk Server
中的消息处理和编排引擎有关的所有处理。
略有不同的两个服务是 Tracking 和 MSMQT
服务。通过 Tracking
服务,可在宿主级别指定该宿主的宿主实例是否参与跟踪进程。这意味着某些服务可能不加载和启动
Tracking
服务,除非宿主已配置为要那样做。这可以将数据的跟踪移动到特定的宿主,并且避免正在进行处理的宿主被跟踪数据压垮。MSMQT
服务(实际上是一个传输适配器)负责实现该适配器所依赖的
MSMQT 基础结构。该服务是传统的 Microsoft
消息队列 (MSMQ) 服务的 BizTalk Server 版本。
对于独立宿主,Endpoint Manager
是加载到该进程中的唯一服务。这些类型的宿主只准备用于承载发送和/或接收消息的适配器。
消息和消息属性
BizTalk Server 2004
实质上是一个消息处理引擎。要了解 BizTalk
Server 2004
的细节,很重要的一点是了解消息以及 BizTalk
Server
如何表示、存储和处理消息。了解什么是消息以后,便可更容易地了解
BizTalk Server 处理消息方式的细节。
可将 BizTalk Server
中的每个消息都视为一个多部分消息,每个消息由零个或多个部分组成。每一部分都包含一个二进制数据块,表示
XML 文档、平面文件、序列化的 .NET
类和其他二进制数据流。每个具有一个或多个部分的消息都将其中一个部分标识为正文部分。可以使用消息的正文部分来标识可用于路由的消息的类型。
一个需要理解的非常重要的概念是:在 BizTalk
Server 2004
中,所有消息都是不可改变的。这意味着消息构造后便不能更改。对该消息进行任何更改时,都需要创建一个新消息并即时使用。这一点在
Orchestration Designer 中格外明显 —
在这里,编译规则强制您遵循关于消息在使用前进行构造的严格准则,并且不允许在消息的构造块外部对其进行更改。如果要更改该消息,则必须创建一个新的构造块,从而创建一个同一类型的消息,将原始消息复制到新消息,然后在离开该构造块之前对新消息进行任何更改。
BizTalk Server
在其消息处理过程中遵循上面这些相同的规则。当在发送端口中处理消息时,如果要在处理过程中更改该消息,则管道组件会创建一个新的消息实例。任何由使用
BizTalk Server
的开发人员创建的管道组件也应该遵循这一做法。
消息必须是不可改变的原因是,对于给定的消息,可能有很多接收该消息的订户。从物理上看,虽然只有一个消息,但是存在很多对该消息的引用。允许某个订户更改消息会影响所有其他订户。例如,假设一个消息由
BizTalk Server
接收并路由到两个不同的编排。如果允许第一个编排更改该消息,则该更改对第二个编排的影响是未知的,并且几乎肯定是不受欢迎的。
除了构成消息的各个部分以外,系统中的每个消息都具有一组伴随的属性,这些属性位于消息上下文
中。这些属性可以是从该消息本身中提取的值,也可以是与该消息有关或与该消息的处理有关的值。例如,适配器将与消息的接收有关的属性放到上下文中,例如,接收该消息的位置以及使用哪个类型的适配器来接收该消息。可以将属性写入上下文或提升到上下文中。这两种选择之间的区别是:可以在消息路由过程中使用提升的属性作为准则,而不能使用写入的属性。
这一将值写入或提升到上下文的概念与在
BizTalk Editor
中提升属性有关,但不完全相同。在 BizTalk
Editor
中,可以将架构中的元素或属性标记为提升属性或可分辨字段。标记为提升属性的项将提升到上下文中,而那些标记为可分辨字段的项将写入到上下文中。
提升属性的设计从设计消息相关开始:将要接收的消息与已经运行的编排实例相关联的能力。对于相关,需要定义一个属性或一组属性,以便提供编排中的消息之间的链接。例如,在采购过程中,需要基于
PurchaseOrderID
将消息相关联。但是,在很多业务场合下,消息中的特定字段或属性的名称可能不匹配。采购订单架构可能有一个名为
POId 的元素,而相应的发票架构可能有一个名为
OrderID
的元素。在这种情况下,要将消息与命名属性(如
PurchaseOrderID)相关联,开发人员必须能够从值的源提取要与其相关联的属性名称。属性架构允许这一提取操作。
属性架构使您能够在公共位置定义提升属性,并且让其他架构引用它们。像其他架构一样,属性架构有命名空间;但是,与其他架构不同的是,它只有已定义的元素(即,不能具有记录或属性)。在属性架构中定义的每个元素都有名称和类型。因为属性架构可能需要由一个以上的架构引用,并且因为属性架构中的信息必须可在运行时供组件使用,所以必须像所有其他架构一样将属性架构部署到
BizTalk Server。除了正常的架构部署步骤以外,有关提升属性的信息还被提取并存储到
Management 数据库中的 bts_documentSpec 表中。
创建属性架构之后,可以将相同类型(例如,Interger)的元素和属性作为属性架构中的命名属性之一加以提升。
要完成上述示例,开发人员需要执行下列步骤,以定义相关所需的共享属性。
1.
|
创建一个属性架构,定义一个 xs:int
类型的名为 PurchaseOrderId 的元素。
|
2.
|
创建一个 PurchaseOrder 架构,添加一个 xs:int
类型的名为 POId 的元素或属性。
|
3.
|
使用 BizTalk 编辑器中的 show promotions
命令,开发人员将 POId
字段添加到提升属性列表,并指示应该通过从该列表中选择
PurchaseOrderId
命名属性,将其提升为属性架构中定义的
PurchaseOrderId 属性。
|
4.
|
创建一个 Invoice 架构,添加一个 xs:int
类型的名为 OrderId 的元素或属性。
|
5.
|
使用 BizTalk 编辑器中的 show promotions
命令,开发人员将 OrderId
字段添加到提升属性列表中,并且指示应该通过从该列表中选择
PurchaseOrderId
命名属性,将其提升为属性架构中定义的
PurchaseOrderId 属性。
|
既然该定义存在于文档架构中,那么管道组件就可以正确地将
OrderId 和 POId 提升为命名属性 PurchaseOrderID,以便可以将其用于路由和相关。有关该提升过程的详细信息,请参阅本文档中的“消息处理”主题。
提升属性的好处之一是:可以在消息的上下文中使用被提升的元素的值。这意味着检索该值不用付出多大代价,因为它不需要将消息加载到内存中然后对该消息执行
XPath
语句。相反,可以结合使用一个简单的属性包与一个键来获取该值。此类型的行为在除消息路由以外的场合下是令人满意的,并且它是创建可分辨字段的动机。提升属性提升到消息上下文中,而可分辨字段是写入消息上下文的。但是,与提升属性不同的是,对于可分辨字段,没有属性架构。这就是不能将可分辨字段用于路由,因而不能在发送端口或编排接收形状中将其用作筛选准则的原因。但是,可以在编排中使用可分辨字段,以便在消息上下文中读取或写入值,而不必将消息加载到内存中并提取该值。
除了将属性提升或写入到消息上下文中以外,还可以将消息谓词添加到该上下文中。消息谓词在
BizTalk Server
中用于安全措施,并且提供有关消息的上下文信息
—
该信息必须与为该消息将要路由到的任何宿主指定的值匹配。该安全措施使您可以对
BizTalk Server
环境进行配置,以便使特定的宿主成为可以接收和处理特定消息的唯一宿主。例如,在
BizTalk Server
管理控制台中,可以用证书的签名来配置宿主,以便将其用于消息解码和解密。配置该属性可以在
MessageBox
中为该宿主创建应用程序属性。使用该签名接收和解密的安全消息只能路由到配置了该签名的宿主。
管道
管道是 BizTalk Server 的组件,它提供管道和筛选器集成模式的实现。有关 Enterprise Integration
Patterns 的详细信息,请参阅 MSDN 文章 Integration Patterns。在消息的接收和发送过程中,因一些业务的原因需要对消息执行转换,以便为它们进入或离开
BizTalk Server 做好准备。一个常见的示例是,可能需要将逗号分隔的平面文件转换为 XML 文件,以便利用
BizTalk Server 中的某些功能(如映射);平面文件分解器组件即完成这一工作。在集成方案中,一种常见的需要是在接收或发送消息之前对它执行多种类型的转换;这就是管道发挥作用的场所。管道使开发人员能够定义在接收或发送过程中对消息执行的一系列转换。
有两种类型的管道:接收和发送 —
它们与各自执行时所用的端口相匹配。发送管道
是在发送端口和请求/响应接收端口的响应部分中执行的,而接收管道在接收位置和要求/响应发送端口的响应部分中执行。实质上,接收管道
准备用于转换要发布到 MessageBox
的消息,而发送管道准备用于已经被订阅并且要发送到
BizTalk Server 外部的消息。
每个管道都具有一组在该管道执行时按顺序执行的阶段。每个阶段都可以包含零个或更多个组件。组件的最大数量取决于阶段。接收管道(如下图所示)具有下列阶段:
解码
|
解密或解码消息数据
|
分解
|
将交换分解为较小的消息,并且分析消息内容
|
验证
|
验证消息数据(通常是针对架构)
|
解析部分
|
标识与消息或消息上下文中的某个安全标记相关联的
BizTalk Server 部分
|
图 2 接收管道
发送管道(如下所示)有三个用于处理组件的阶段:
预组装
|
在组装消息之前执行任何必要的消息处理
|
组装
|
通过采取诸如添加信封、将
XML
转换为平面文件这样的操作或其他对接收管道中的分解阶段起补充作用的任务,组装消息并使其做好传输准备
|
编码
|
在传递之前将消息编码或加密
|
图 3 发送管道
管道中的阶段具有执行模式All 或 First
Match,它们在多个组件添加到该阶段时控制执行的组件。对于具有
All
模式的阶段,将按照在该阶段中配置组件的顺序来调用每个组件,以便处理消息。当模式为
First Match
时,将轮询每个组件(指示该组件是正确的组件)直到找到匹配的组件为止,
—
此时,将执行匹配的组件,而不会执行其余组件。
作为执行模式的示例,接收管道的 Disassemble
阶段是第一个匹配阶段,因此该阶段内的每个组件都被调用,以查看它是否能够识别并处理信息。如果确定组件的响应,则不会再查询该阶段中的其他组件以查看它们是否也可以处理该消息。但是,接收管道的
Decode 阶段具有执行模式 All,这意味着该阶段中的每个组件都将按照其配置顺序加以调用,以便处理消息。第一个解码器可能要解密消息,而第二个解码器可能要从压缩格式解压缩该消息。
当开发人员要在单个接收管道中使用多个分解器时,管道处理中的执行模式会有一个共同的结果。通常,分解组件之间区别甚微,例如,两个配置类似(但不同)架构的平面文件分解器。在这种情况下,虽然消息实际上可能与所配置的第二个分解器中定义的架构匹配,但第一个分解器可能通过试探来确定它是否可以处理该消息。只有在处理消息以后,才能发现错误并且将消息挂起。在上述情况中,正确的选择是:创建一个新的、在内部有更为具体的试探逻辑的分解器;或者创建两个不同的管道,在不同接收位置接收不同的消息。
消息处理
迄今为止描述的所有组件都会在消息流经
BizTalk Server
时对其进行处理。本节提供有关这些组件如何交互以使消息处理基础结构进行工作的更多详细信息(从接收消息开始)。下图显示接收端口的构成以及贯穿于接收过程中的消息流。
接收端口
由一个或多个接收位置以及零个或多个映射组成。映射是用于将消息从一种结构或格式转换到另一种结构或格式的
XSLT
样式表,且通常在接收过程中使用以将消息规格化为内部格式。消息由单个接收位置接收到接收端口中。接收位置由接收适配器和接收管道组成。
图 4 接收端口和消息流
接收适配器通过读取一个数据流并创建一个消息来启动接收消息的过程。例如,文件适配器发现一个文件已放到它的配置位置,并且流式读取该文件。该适配器创建一个消息(Microsoft.BizTalk.Message.Interop.IBaseMessage
接口的实现),向其添加一个部分(Microsoft.BizTalk.Message.Interop.IBasePart
接口的实现),并将该数据流作为部分的内容。
此外,该适配器将属性写入和提升到与该位置、适配器类型和其他适配器专用属性有关的消息上下文。在该消息及其上下文创建之后,适配器将该消息传递给
Transport Proxy(也由 Endpoint Manager
管理)。然后,该消息通过已经为接收位置配置的接收管道进行传递。在该消息由管道处理以后,可能使用一个映射将该消息转换为所需的格式,然后由
Endpoint Manager 将该消息传递给 Message Agent
以便进行发布。
虽然由适配器负责创建初始消息,但是在接收的消息上发生的大多数处理都发生在接收管道中。管道处理过程需要处理消息内容和消息上下文。消息内容通常在解码、分解和验证阶段处理,而消息上下文可以在所有阶段处理。但是,管道未必一定要影响内容或上下文;例如,默认的传递管道没有配置任何组件,并且不对消息内容或上下文执行任何处理。为简单起见,本文重点讨论分解组件,因为它们通常对消息路由有最大的影响。
分解器负责处理来自适配器的传入消息并将其分解为很多个消息,然后分析消息数据。当传入消息具有很多个较小的消息时,这称为交换。通过使开发人员能够配置有关包装内容(对于平面文件分解器,是标头和后缀架构;对于
XML
分解器,是信封架构)的信息以及很可能重复的正文内容的信息,平面文件分解器和
XML
分解器对交换进行处理。此外,这两个分解器都将原始消息分析为
XML 内容。如果不需要在 BizTalk Server
中进行进一步的 XML
处理,则自定义分解器不需要将内容分析为 XML。示例方案可能是这样一个简单的路由情况:其中,在特定接收位置进入系统的消息被发送到特定的发送端口,而不进行任何映射或其他基于
XML 的处理。
在路由过程中使用的最常见的消息属性之一是消息类型。当开发人员创建架构以定义消息的结构时,该架构就定义该消息的消息类型。该类型由架构定义中的根节点和命名空间确定。例如,如下所示的
XML 文档具有消息类型 http://tempuri.org/samples/MessageType#Message。
<Message xmlns=http://tempuri.org/samples/MessageType>
<SomeOtherElement type="sample"/>
</Message>
请注意,消息类型由后跟井号(#)的命名空间以及根节点的名称组成。要在路由过程中使用消息类型,必须将其提升到上下文中。分解器通常负责将该值提升到消息上下文以及具有最适合的消息结构内容的管道组件中。XML
和平面文件分解器在处理消息的过程中提升消息类型,并且任何自定义分解器也都应该提升该属性以确保正确的路由。
特别强调,不要求消息具有类型。正如前面提到的,消息的各个部分可以是任何二进制数据,并且不需要具有用来定义它们的结构的架构。该类型的消息部分通常通过
BizTalk Server 传递,并且不会由 BizTalk Server
本身对其进行很多处理(如果处理),尽管自定义管道组件、适配器或从编排中调用的代码可能与这些部分进行交互。
管道组件(如适配器)还将属性写入和提升到消息上下文中。实际上,大多数开发人员利用管道组件这一最常见的机制将属性放入消息上下文中。正如在“消息和消息属性”主题中讨论的,开发人员创建架构,提升该架构中的属性或将它们标志为可分辨字段。该信息作为批注存储在架构中,并且随后可供管道组件使用。所有内置分解器和组装器组件
— FlatFile、XML 和 BizTalk Framework —
都使用文档架构来检索有关要提升的属性的信息。通过批注中的
XPath
语句,分解器可了解要提升的元素在文档中的位置。在对该文档进行流式处理的过程中,分解器查找与某个
XPath
语句匹配的元素并根据需要将值提升或写入到上下文。
还可以编写自定义管道组件,以将属性放入已接收或发送消息中任意数据的上下文。为了将属性提升到该上下文并使其可用于路由(这大概是提升该值的原因),应该创建一个带有该属性的定义的属性架构并将其部署到
BizTalk Server。在定义由自定义组件使用的属性架构之前,应该了解不同类型的提升属性。在属性架构中定义的提升属性可具有下列两个基本类型之一:MessageContextPropertyBase
或 MessageDataPropertyBase。
具有基本类型 MessageDataPropertyBase
的属性表明该属性的值来自消息的内容。这是在属性架构中定义的属性的默认值,并且是最常见的用法。MessageContextPropertyBase
指示一个确定为消息上下文一部分(但未必直接来自消息数据)的属性。具有
MessageContextPropertyBase(为属性的基本类型)的属性,通常由适配器和分解器提升并包括诸如消息类型和适配器类型等常见属性。
定义属性时,重要的一点是了解不同的类型并适当地使用它们。访问编排中消息的上下文属性时,将面临一个最重大的暗喻。如果属性被标识为
MessageDataPropertyBase,则编排设计器分析所接收消息的架构,并确保定义匹配的提升属性。如果没有在与所访问的提升属性有关的架构中找到属性,则该分析器不允许对其进行访问。另一方面,如果该属性被定义为
MessageContextPropertyBase,则消息类型无关紧要,并且可以访问该属性。
在自定义管道中,用于将属性提升或写入到上下文的机制非常类似。对于写入属性,可以使用对
IBaseMessageContext.Write
方法的调用将值放到上下文中。对于提升属性,只需使用
IBaseMessageContext.Promote
方法。上述每个方法都采用属性名、命名空间和值作为参数。对于提升属性,名称和命名空间属于在属性架构中定义的属性,并通过引用属性架构程序集以及使用为该属性创建的类上的属性,可以最轻松地进行访问。可分辨字段使用公共命名空间
http://schemas.microsoft.com/BizTalk/2003/btsDistinguishedFields,而用于检索值的
XPath 表达式通常用作名称。
下面的代码显示将属性提升和写入到上下文的示例。请注意,在该示例中,一个可分辨字段写入到上下文。仅对编排有用
—
在编排中,消息架构标识可分辨字段,因此编排设计器了解该字段。将属性写入到上下文以供接收或发送端的其他管道组件使用可能是有用的。
//create an instance of the property to be promoted
.MethodName methodName = new SOAP.MethodName();
//call the promote method on the context using the property class for name
//and namespace
.Context.Promote(methodName.Name.Name, methodName.Name.Namespace,
"theSOAPMethodName");
//write a distinguished field to the context
.Context.Write("theDistinguishedProperty",
"http://schemas.microsoft.com/BizTalk/2003/btsDistinguishedFields",
"theDistinguishedValue");
将值写入或提升到上下文时,需要记住几个事实。首先,通过先前用于提升该属性的相同名称和命名空间将值写入到上下文中,将导致该属性不再被提升。写操作实质上重写了提升操作。其次,由于不允许出现空值属性,因此将空值写入上下文中会删除该值。最后,提升属性的长度被限制为
256
个字符,而写入属性没有长度限制。提升属性用于消息路由过程,并出于比较和存储方面效率的原因,在大小上受到限制。尽管写入属性在大小上没有硬限制,但在上下文中使用过大的值仍然会对性能产生影响,因为这些值仍然必须与消息一起处理和传递。
当消息准备好从 BizTalk Server
发送时,它将在发送端口中经受辅助性的处理。在执行发送管道之前,映射要应用到消息,可以首先将消息转换为客户或应用程序特定的格式,然后再由管道处理并通过适配器发送。在发送管道中,属性从上下文中降格到消息而不是提升到消息上下文中。只有对于
MessageDataPropertyBase
属性,才会发生该操作,因为它们是影响消息数据的唯一的提升属性类型。
图 5 发送端口和消息流
BizTalkServer 中的订阅
在发布/订阅设计中,有三个组件:发布者、订户和事件。BizTalkServer
中的事件 仅指消息。发布者
包括接收端口、编排和发送端口。发布者
包括:接收端口(发布到达其接收位置的消息)、编排(发送消息或异步启动另一个编排时发布消息)和请求-响应发送端口(从目标应用程序或传输中接收响应时发布消息)。
在 BizTalk Server 中,有两种主要类型的订阅:激活
和实例。激活订阅,指定完成该订阅的消息应该在其接收时激活或创建订户的一个新实例。激活订阅的创建者包括带有筛选器的发送端口或被绑定到编排的发送端口,以及将它们的“Activate”属性设置为
True
的编排接收形状。实例订阅表明完成该订阅的消息应该路由到订户的已经运行的实例。实例订阅的创建者包括带有相关接收的编排以及正在等待来自
BizTalk Server 的响应的请求/响应样式接收端口。
这两种类型的订阅在信息级别的差异是:实例订阅包含唯一的实例
ID(存储在主 MessageBox
中的订阅表中)。当编排实例或接收端口完成处理时,实例订阅从
MessageBox 中移除,而激活订阅仍然处于活动状态
—
只要编排或发送端口进行了登记。有关编排如何与消息处理系统交互的详细信息,请参阅本文档中的“编排和消息处理”主题。
创建订阅
订阅由 BizTalk Server
中的服务类创建,这些类列于 BizTalk Management
数据库中的 adm_ServiceClass
表中。这些服务在前文中的“宿主和宿主实例”部分中进行了鉴别,包括:缓存服务;进程内和独立消息处理(由
Endpoint Manager 承载);编排/XLANG(由 XLANG
子服务承载);以及 MSMQT。上述每个服务类都可以创建订阅以及接收发布的消息。
订阅 是一个比较语句(称为“谓词”)集合,涉及到消息上下文属性以及特定于订阅的值。例如,消息类型是消息的上下文属性,并且很多订阅指定消息类型。有关订阅的常规信息由
Message Agent
插入到订阅表中,而具体的谓词进入下列谓词表之一,具体取决于为该订阅指定的操作类型:
|
• |
BitwiseANDPredicates
|
|
• |
EqualsPredicates
|
|
• |
EqualsPredicates2ndPass
|
|
• |
ExistsPredicates
|
|
• |
FirstPassPredicates
|
|
• |
GreaterThanOrEqualsPredicates
|
|
• |
GreaterThanPredicates
|
|
• |
LessThenOrEqualsPredicates
|
|
• |
LessThenPredicates
|
|
• |
NotEqualsPredicates
|
所有上述操作均通过调用
MessageBox 中的
Bts_CreateSubscription_<application
name> 和
Bts_InsertPredicate_<application
name>
存储过程完成,其中,
是将要创建该订阅的
BizTalk
宿主的名称。
当发送端口被登记时,该端口将至少为任何在上下文中带有该发送端口的传输
ID
的消息创建一个订阅。这使该发送端口总是可以接收专门为它准备的消息。当编排端口绑定到特定的发送端口时,有关该绑定的信息将存储到
BizTalk Configuration
数据库中。当消息从编排中通过绑定到物理发送端口的端口发送时,传输
ID
将包含在上下文中,以便能够将消息路由到该发送端口。但是,需要说明的是,该发送端口不是可以接收从该编排发送的消息的唯一发送端口。当编排发送消息时,该消息将与所有相关的提升属性一起发布到
MessageBox。绑定的发送端口保证能够收到该消息的副本,因为传输
ID
位于上下文中;但任何其他发送端口或编排都可以具有还与消息属性匹配的订阅。需要了解的一个要点是:每当消息直接发布到
MessageBox
时,所有具有匹配订阅的订户都将收到该消息的副本。
发布和路由
创建和启用订阅以后,必须首先发布消息,然后才能执行任何处理。当消息从先前提到的服务之一接收到
BizTalk Server
中时,将发布消息。对于这里的路由讨论,我们将重点探讨通过适配器接收到
BizTalk Server
中的消息。
当消息经受接收管道处理时,属性被提升到消息上下文中。在消息被接收适配器和管道处理并且做好发布到
MessageBox
中的准备以后,发生的第一件事情是
Message Agent
将提升属性的属性值和谓词值从消息上下文插入到主
MessageBox SQL Server
数据库中。将上述值放到数据库中使
Message Agent
能够做出路由决策。
下一步是让
Message Agent
要求主 MessageBox
数据库查找要发布的当前这批消息的订阅。请记住,这些消息尚未写入到该数据库中,而只有来自上下文的属性被写入。MessageBox
中的
bts_FindSubscriptions
存储过程查询前文中标识的订阅和谓词表,并且将它们链接到为当前这批消息存储的消息属性。
借助于这一信息,Message
Agent 通过调用
bts_InsertMessage
存储过程将该消息一次性地插入到每个具有订阅的
MessageBox
数据库中。首先,用订阅
ID 调用
bts_InsertMessage
存储过程。在第一遍,该存储过程调用
int_EvaluateSubscriptions
存储过程,后者负责查找订阅详细信息,通过检查消息谓词是否与宿主的应用程序属性匹配来验证消息满足应用程序的安全要求,并且根据状态,将对该消息的引用插入到应用程序专用队列或应用程序专用挂起队列中。对于为该应用程序找到的每个订阅,将消息
ID、订阅 ID、服务
ID
和其他订阅信息插入到应用程序专用队列表中。在插入消息以后,从消息属性和消息谓词表中清除与该批处理有关的值。
随后,对消息中的每个部分调用
bts_InsertMessage
存储过程。在首次调用时,将消息上下文与有关该消息的元数据(如部分个数、正文部分名称和
ID)一起传递并随后插入到
SPOOL
表中。此外,还使用
int_InsertPart
存储过程将消息正文部分插入到
PARTS
表中。然后,对其余消息部分中的每一个部分,调用
bts_InsertMessage
存储过程;此时,它们只是被传递给
int_InsertPart
存储过程,以便在
PARTS 表中暂存。
当消息被路由时,通过向下列表中插入记录,为接收该消息及其各个部分的特定实例的每个服务添加引用:
|
• |
MessageRefCountLog1
|
|
• |
MessageRefCountLog2
|
|
• |
PartRefCountLog1
|
|
• |
PartRefCountLog2
|
这些引用可以防止消息及其各个部分被清理作业(它们定期运行,以防止
MessageBox
充满系统中不再存在的消息的消息数据)删除。两个表可用于减少争用和锁定问题。
既然消息已路由到正确的队列,存储在 Spool 和 Parts 表中,并且在应用程序专用队列中引用,那么这些消息必须由应用程序实例从队列中取走。每个宿主实例都具有一些出列线程,这些线程按照在
BizTalk Management 数据库中的 adm_ServiceClass 表中配置的时间间隔不断轮询数据库。同一个表还具有一个指示要使用的出列线程的数量的列。有关如何调整这些参数的信息,请参阅
BizTalk Server 2004 Performance Characteristics。每个线程都调入到 MessageBox
数据库中,并且调用与它所在的宿主应用程序相适合的 bts_DequeueMessages_<application
name> 存储过程。该存储过程使用锁定语义来确保在给定时刻,只有一个实例和一个出列线程能够对队列中的消息执行操作。获得锁的宿主实例获得该消息,并且随后负责将该消息转交给相应的子服务。
如果接收该消息的服务是
Endpoint
Manager,则调用发送端口(或请求/响应接收端口的响应部分);而且,如果该服务是
XLANG/s
子服务,则根据订阅中是否存在实例
ID,创建或者查找一个编排来为该订阅提供服务。然后,该服务释放对该消息及其各个部分的引用,以便如果没有其他服务具有引用,则可以删除消息数据。
编排和消息处理
编排可以通过
MessageBox
订阅(接收)和发布(发送)消息。此外,编排可以构造新的消息。本文已经讨论了通过订阅和路由基础结构接收消息。当为编排填充订阅时,将激活一个新的实例并且传递该消息;或者,对于实例订阅,将根据需要重新补充该实例,然后传递该消息。当从编排中发送消息时,它们按照与到达接收位置的消息相同的方式发布到
MessageBox,并且将适当的属性插入到数据库中以供在路由过程中使用。
在编排中构造的消息必须暂存在
MessageBox
中,并且由编排实例引用,但因尚未发送,所以不应该发布它们。XLANG/s
子服务对
Message
Agent
API
进行调用以直接插入消息。这使该引擎可以将消息正文插入到
MessageBox
中,并且将其直接与正在运行的编排实例相关联。所构造的消息在
MessageBox
中的暂留与编排中的暂留地点相协调,以便对数据库操作进行额外的优化。
编排中使发布和订阅看起来具有不同行为方式的概念是绑定。编排端口是描述交互的逻辑端口。这些逻辑端口需要绑定到某个物理端口,以便实际传递消息,但这一绑定过程只是配置订阅以便进行消息路由。有四个用于绑定这些端口的基本选项:
• |
立即指定(直接在编排中指定它们)
|
• |
以后指定(在部署时指定它们)
|
• |
使用动态发送端口,其中,地址在编排代码中设置
|
• |
从该编排到
MessageBox
或另一个编排创建直接绑定
|
在设计时指定绑定时,将在部署该编排时创建一个与在该编排中配置的参数匹配的物理端口。在部署时配置绑定时,可以将任何与逻辑端口的需求匹配的端口绑定到编排端口。对于动态绑定,就像“立即指定”选项那样创建物理端口,但该端口是没有配置任何地址信息的动态发送端口。
让很多开发人员感到困惑的一个概念是,尽管编排中的发送端口被绑定到物理发送端口,但这并不妨碍消息被传递到其他订户。也就是说,如果另一个发送端口恰好通过其筛选器具有针对发送到绑定端口的消息的订阅,则这两个发送端口都将收到该消息。绑定操作只是创建该订阅,以便从编排中发送的消息总是与绑定发送端口的准则相匹配。同样,绑定到接收端口的编排端口基于消息类型和接收端口
ID
来创建适当的订阅。该订阅可以确保进入和离开该编排的消息被传递给绑定端口,但该消息仍然接受先前描述的相同的发布和订阅机制的处理。
可能最容易误解、误用或未充分利用的绑定选项是直接绑定选项。直接绑定使编排可以通过各种路由属性将消息发布到
MessageBox,这非常类似于消息被接收位置发布。在简单的直接消息处理中,消息发布到
MessageBox,该消息的提升属性像接收到
BizTalk
中的任何其他已发布消息那样路由。这使任何订户都可以接收该消息,但要求至少存在一个订户;否则,编排将导致由于发生路由故障而引发异常。有关路由故障的详细信息,请参阅本文档中的“路由故障”主题。
直接绑定的另一个选项是使用自相关端口。自相关端口
是指创建唯一的相关标记并且独自使用该标记来在实例之间对消息进行关联的端口。自相关端口的最常见的用途是调用或启动编排以传入端口参数。在调用的编排中,可以使用该端口来发送消息;而在调用编排中,可以使用同一个端口来接收消息。因为该端口具有唯一的相关标记,所以消息被重新路由到调用编排。自相关端口的行为方式类似于编排实例之间的专用通信信道。
最后一个选项是使用伙伴编排,其中,在调用编排和被调用的编排中,都使用相同的共享端口类型来配置该端口,而在端口配置中,选择相同的端口。例如,在
Orch1
和
Orch2
中,选择
Orch2.MyDirectPort。该类型的绑定基于发送编排类型、端口名称和操作名称为接收编排设置订阅。这再一次确保消息路由到正确的实例。
对于这些直接消息处理选项以及使用
Start
编排形状来异步启动编排,需要记住的一件事情是它们全都使用基础的发布和订阅模型。这些选项之间的区别在于用于创建订阅和路由的属性以及它们帮助解决的用例中。
在编排中使用直接绑定端口时遇到的一个常见问题是,编排可能发布同时还被订阅的消息。例如,编排被配置为由
PurchaseOrder
消息激活。该编排使用直接端口将
PurchaseOrder
消息发布到
MessageBox。但是,除了按照预期接收该消息以外,另一个编排实例也被启动,因为它也具有针对
PurchaseOrder
消息的订阅。该处理进入无穷循环中,并且开发人员可能需要花费一些时间来弄清楚发生了什么事情。
相关
编排中的相关是能够将相关的消息接收到同一个正在运行的编排实例中的机制。在
Orchestration
Designer
中,为了使用相关,开发人员可以按照下列常规步骤操作:
• |
定义一个相关类型,使其包含用于对消息进行关联的提升属性。
|
• |
定义一个相关集(它是刚才定义的相关类型的实例)。
|
• |
在发送和接收端口上指定它们是创始还是遵循给定的相关集。
|
当相关集被创始
时,实例订阅将发挥作用,因为正是在这时为所有遵循该相关集来接收消息的端口创建订阅。因为相关类型定义了要用于相关的属性,所以编排引擎可以从由创始操作发送或接收的消息中提取这些属性。然后,这些值用于为其余所有遵循该相关集的操作定义订阅。
下图,在点
1
处发生的发送操作初始化一个相关集。此时,因为点
2
处的接收形状配置为遵循该相关集,所以为包含该消息类型和相关属性的接收端口创建了一个实例订阅。
图
6
初始化相关集的发送操作
鉴于相关的工作方式,很重要的一点是被接收到
BizTalk
Server
中并且准备在相关中使用的消息正确地定义它们的提升属性并且将其正确地提升到消息上下文中。为了使大多数属性能够在消息最初被接收时得到提升,使用分解器组件来提取值。因此,使用传递接收管道来接收必须与正在运行的编排实例相关的消息是不可能的。这是一个在使用
SOAP
接收适配器来接收相关消息时出现的常见问题,因为在使用
Web
服务发布向导时,接收管道的默认值是传递管道。
BizTalk
Server 中的请求/响应消息处理
在请求/响应消息处理模式中,一方发送请求消息,而接收方返回响应消息。两个典型的请求/响应处理示例是浏览器与使用
HTTP
协议的
Web
服务器之间的交互以及使用
SOAP
协议的
Web
服务处理。在
BizTalk
Server
2004
中,请求和响应消息都以典型的发布/订阅方式处理。对于
BizTalk
应用程序的性能调整,这是一个需要了解的重要注意事项,因为需要较高吞吐量的系统的配置方式可能与需要单个消息具有较低等待时间的系统不同。
图
7
请求/响应消息处理
当消息被请求/响应样式接收适配器接收时,BizTalk
首先将该请求消息发布到
MessageBox。接下来,消息由适当的订户接收,该订户可能是绑定到接收端口的编排。该订户创制一个响应消息,并且将它与导致它被发送到发出请求的接收端口的属性一起发布到
MessageBox。最后,该响应消息由请求的发布者(提交该请求的接收适配器)获取,并且返回到调用应用程序。下图提供上述步骤的详细图形表示。
图
8
消息处理分步演示
由
SOAP
适配器接收的请求/响应消息流
1.
|
SOAP
适配器向
Endpoint
Manager
提交消息。
|
2.
|
Endpoint
Manager
使用
Message
Agent
API
将该消息插入到
MessageBox
中。
|
3.
|
编排(它被绑定到接收端口,因此具有针对该消息的订阅)接收该消息并处理它。
|
4.
|
该编排发送一个响应消息,后者被发布到
MessageBox。
|
5.
|
Endpoint
Manager
接收响应消息。
|
6.
|
Endpoint
Manager
将该响应返回到
SOAP
适配器。
|
如果不了解内部实现,则该类型的行为对于性能的含义可能被忽视。BizTalk
Server
最初是为高吞吐量方案而进行优化的,但可能为具有较低吞吐量的环境和较低等待时间的需要(尤其是在请求/响应方案中)进行配置。对于该方案中的优化,需要考虑多个组件。首先,订户通过一个轮询机制来了解有关已发布的消息的信息。如果轮询时间间隔设置得太高,则可能导致请求/响应样式交互具有高于期望值的等待时间。
请注意,在该方案中,需要填充两个订阅:对初始消息的订阅以及对响应消息的订阅;而这又增加了该轮询时间间隔的影响。其次,接收适配器配置为将消息按照大小不同的批量插入到
MessageBox 中。大多数适配器使您能够通过典型的适配器配置 UI 或者通过 BizTalk Server 中的参数或注册表来配置批量大小。如果批量大小设置得太高,则单个消息的等待时间可能增加。有关
BizTalk Server 2004 的性能特征以及如何针对较低等待时间方案进行优化的详细信息,请参阅 BizTalk
Server 2004 Performance Characteristics。
路由故障
当对于发布到
BizTalk
MessageBox
的消息没有匹配的订阅时,就会发生路由故障。出现该行为的原因是:没有订户,该消息将写入到
MessageBox
中,并且可能永远不会被获取。路由故障表明,消息发布到
MessageBox
并基于该消息的属性,未找到任何订阅。路由故障消息的上下文是发布到
MessageBox
的消息的同一个上下文。这使开发人员或管理员可以分析属性,并且将它们与应该在该系统中的活动订阅进行比较,以便确定该消息未正确路由的原因。
通常,发生路由故障有很多已知原因。最明显的原因是该消息的属性只是不与活动订阅匹配。有两种情况最为经常地导致属性不匹配:未登记带有适当筛选器的发送端口或编排,或者该消息在消息属性提升时不包含正确的数据。在第一种情况下,登记发送端口或编排可以解决问题。在第二种情况下,需要检查创建或改变该消息的过程,以确保它正确插入了将提升属性用于路由所需的全部数据。属性未获提升的一个常见原因是使用了传递管道。该管道内部没有用于提升属性的分解器。应该改而使用
XML
接收管道或能够提升消息属性的自定义管道。
另一种出现路由故障的情况存在于请求/响应方案(如
Web
服务接收位置中),即无法将响应消息重新路由到请求者的时候。该类型的故障的一个常见原因是:Web
服务客户端连接在等待编排完成并发送响应时超时。在这种情况下,在
Web
服务客户端超时以后,来自编排的响应消息被发布,但不存在针对它的订阅。
要调试路由故障,将在运行时可用的消息属性与系统中的活动订阅进行简单的比较,通常可以确定问题的性质。要分析消息属性,可以使用
Health
and
Activity
Tracking
工具。路由故障出现在“Operations/Messages”搜索下面。右键单击该消息并且选择消息详细信息,可以调出一个包含消息上下文的属性窗格。该消息上下文可以打开,并且该消息上的所有属性都将是可见的。这就公开了实际存在的属性,但您可以使用另一个工具来分析订阅。BizTalk
Server
2004
SDK
包含“订阅查看器”工具。该可执行文件显示系统中的活动订阅,并且使您能够查看订阅中包含的属性。对比这两个数据点之后,便一幕了然为什么没有与入站消息匹配的订阅了。
使用订阅查看器
订阅查看器是
BizTalk
Server
2004
中随附的一个有用工具。该工具位于
<Install
Directory>\SDK\Utilities
文件夹中。该工具的目的是向开发人员和
IT
专业人士提供给定
Server
安装中的活动订阅的视图。本节旨在提供有关如何理解该工具中呈现的数据并且使用它来对路由和订阅问题进行疑难解答的信息。
图
9
订阅查看器工具
订阅查看器将所有活动订阅加载到窗口中。在第一列中,订阅的名称可使您了解订阅的类型。编排激活订阅以“Activate”开头;实例订阅以“s”开头。发送端口订阅同时使用端口名称和该发送端口的传输
ID
命名。该查看器中还可能出现两种其他类型的订阅:第一种是用于启动或调用编排的
GetObject
订阅;最后一种用于内部订阅,在该查看器中没有名称。
该查看器显示是启用还是禁用订阅。这一指示器显示是否登记编排或发送端口。如果订阅未启用,则不会将消息路由到服务;如果没有其他服务具有针对该消息的订阅,则会发生路由故障。该查看器还显示创建该订阅的其他子服务:EPM
for
Endpoint
Manager、用于编排的
XLANG。
要获得有关某个订阅的详细信息,请在网格中突出显示它,订阅详细信息将出现在底部窗格中。位于“AND”筛选器分组中的每一组属性都在底部窗格中显示它们自己的分组。各组之间的分隔符在筛选器分组中表示“OR”条件。要调试订阅问题,可以将底部窗格中的这些属性与在入站消息中找到的消息上下文属性进行比较。
使用多个
MessageBox
以前版本的
BizTalk
Server
的一个可伸缩性限制与挂起和工作队列处理有关。在消息处理方案中,随着容量的增加,最终会到达这样一个时刻:数据库中的读取和写入活动的数量导致性能下降,并最终由于
SQL
表上的锁争用而恶化。对于
BizTalk
Server
2004,MessageBox
数据库不仅通过使用更大的、更强大的硬件,而且还通过允许将多个
MessageBox
数据库组合为一个虚拟的
MessageBox,提供可伸缩性。如果客户需要扩展其系统的消息处理以超越他们的
MessageBox
数据库的功能,则可以使用多个
MessageBox
数据库来横向扩展他们的基础结构。
正如前面提到的那样,Message
Agent
负责管理与
MessageBox
数据库之间的所有交互,因而,它是唯一一个需要了解多个
MessageBox
数据库并且能够与它们协同工作的组件。因此,开发人员不需要了解如何管理这些数据库以及如何与它们进行交互。相反,他们需要了解如何让多个
MessageBox
数据库影响系统的可伸缩性,以及应用程序设计可能如何妨碍该可伸缩性。
当第二个
MessageBox
数据库添加到
BizTalk
Server
安装时,第一个
MessageBox
仍然充当主
MessageBox。主
MessageBox
的责任之一是处理所有订阅匹配,以及将消息路由到适当的
MessageBox
数据库。实际上,主
MessageBox
确定应该将消息路由到哪里,而
Message
Agent
实际负责将消息发送到正确的
MessageBox
数据库。正如先前在“BizTalk
Server
中的订阅”主题中提到的那样,存在两种类型的订阅:激活和实例。可以将路由到激活订阅的消息路由到任何
MessageBox
数据库,因为它们没有与任何现有实例相联系;但是,必须将实例订阅的消息路由到该实例的激活订阅所发送到的
MessageBox。
需要记住的是,多个
MessageBox
数据库设计旨在提供可伸缩性,但不能提供任何额外的可靠性。MessageBox
数据库的可靠性通过聚集各个
MessageBox
SQL
数据库提供。如果
MessageBox
其中一个数据库变为不可用,则整个
MessageBox
变得不可用。每个
MessageBox
数据库都具有一个针对系统中的所有宿主的工作和挂起队列,并且每个宿主都与所有
MessageBox
数据库交互,尽管它们显然是通过
Message
Agent
层完成该工作的。目前,Message
Agent
使用轮换算法来确定接收消息的
MessageBox
数据库,但是这一点可能更改,您不应该依赖它。在编排实例启动后,它锁定到特定的
MessageBox,并且在这里得到处理。如果该
MessageBox
实例出现故障,则目前没有从这一情况恢复的机制,并且应该路由到该
MessageBox
的传入消息将失败。
BizTalk
Server 和 .NET
很多与使用
BizTalk
Server
有关的客户问题实际上是与
Microsoft®
Visual
Studio®.NET
有关的问题。因为
BizTalk
Server
开发与
.NET
运行库紧密集成,因此需要了解某些部署、编码和管理问题。本节讨论与
BizTalk
Server
和
.NET
有关的关键领域和问题。
尽管
.NET
是在
BizTalk
Server
中进行开发的首选平台,但应该说明的是,BizTalk
Server
的某些领域还支持在
COM
兼容语言中进行开发。例如,管道和适配器相关
API
具有可以在生成组件和适配器时使用的
COM
定义接口。实际上,BizTalk
Server
中的消息处理引擎几乎完全是用本机代码编写的。因为应用程序的这一部分通常是在发布
.NET
时生成的,所以将其更新到
.NET
是不可行的。但是,编排引擎将要被重新设计和重新编写,因此,它很可能将使用
.NET
编写。
BizTalk
程序集
BizTalk
和
.NET
的最重要的方面是所有
BizTalk
Server
构件、映射、架构、编排和管道均编译为
.NET
程序集。这一设计的两个最重要的含义是:这些程序集必须是强名称的;因此,它们也遵循
.NET
版本控制规则。其主要含义是,在针对另一个
.NET
项目/程序集(包括
BizTalk
项目)的特定版本生成一个
BizTalk
项目以后,该项目将一直使用该版本,直到它针对较新的版本重新生成为止。
在开发过程中发生的一个与
.NET
版本控制有关的常见问题发生在以下时刻:BizTalk
项目上的版本号没有更改,而程序集在未停止和启动要将类型加载到的
BizTalk
宿主实例的情况下重新部署。当进程重新运行时,所做的更改不会生效。其原因在于将
.NET
程序集加载到内存中的方式。因为该宿主已经具有该程序集的内存中副本,所以当新的副本放到全局程序集缓存中的时候,它不会重新加载该程序集。例如,如果一个带有编排的程序集的版本
1.0.0.0
被部署并且正在运行,并且对该编排进行了更改,但版本号未更改,则所做更改不会生效。在宿主实例停止以后,该程序集的内存中副本将释放,并且当宿主实例重新启动时,它会重新加载该程序集的新副本,并且获得更改。如果部署新版本(如版本
2.0.0.0)并进行加载,则更改会生效。
将程序集部署到
BizTalk
Server
的过程分为两个部分。第一个部分是传统的
.NET
程序集部署,其中,强名称的程序集被部署到将要使用该程序集的每个服务器上的全局程序集缓存
(GAC)。第二步是将有关程序集及其类型的元数据部署到
BizTalk
Server
Management
数据库。当
BizTalk
程序集由
BizTalk
Server
加载时,它们通常使用其强名称(位于
Management
数据库中)加载。
开发人员创建的
BizTalk
Server
构件编译为从内置的
BizTalk
Server
类型派生的类。例如,编排成为从
Microsoft.BTXEngine.BTXService
类派生的类。正是因为这些基类在程序集中被部署到全局程序集缓存,并且这些程序集对
GAC
中的其他程序集具有依赖项,才必须同时将开发人员的程序集部署到
GAC。
BizTalk
Server
构件被部署到全局程序集缓存并且因而具有强名称的另一个重要含义是,强名称程序集无法调用其他不具有强名称的程序集。这意味着,开发人员创建的任何由这些
BizTalk
程序集使用的程序集也必须是强名称的。同样,部署到
GAC
的、不使用特定路径加载其他程序集的程序集必须从该
GAC
中加载那些程序集。
与开发人员创建的其他很多组件不同,管道组件不会部署到
GAC。管道组件完全使用
BizTalk
接口,因而不会在
GAC
中部署基类。相反,管道组件是使用另一个用于部署
.NET
程序集的常见方法查找和加载的,即将它们部署到已知的位置。管道组件被部署到
BizTalk
Server
安装目录中的一个名为“pipeline
components”的文件夹。
管道组件添加到
Studio
.NET
中的开发人员工具箱中,以便可以将它们拖动到管道设计器中。当
BizTalk
管道被编译为
.NET
程序集时,有关该管道的各种阶段中的所有组件的信息都编译到该程序集中。当该管道部署到
BizTalk
Server
时,有关组件的信息(包括它们的文件名)都插入到
BizTalk
Management
数据库中。当该管道执行时,这些组件从其已知位置加载,并且它们实现的接口根据情况予以调用。
这里,需要说明有关管道组件的两件事情。首先,必须将管道组件依赖的任何程序集部署到
GAC,以便在运行时能够找到它们。这是因为,只有管道组件本身在
BizTalk
中具有使其可以从已知位置加载的元数据。其次,归因于
BizTalk
Server
2006
中的新功能,这一点将要更改,并且管道组件将部署到
GAC
中。
附录
A — 用于监视 BizTalk Server 运行状况的 SQL 查询
以下查询集旨在提供有关
BizTalk
Server
系统的运行状况的有用信息。这些查询就
BizTalk
Server
数据库的位置和名称进行了一些假设。为了使这些查询可在环境中正常工作,则可能需要对它们进行一些小的改动。如果您不是合格的
BizTalk
Server
管理员或开发人员,或者不了解这些查询是做什么的,那么就不应该修改它们或者在环境中使用它们。
重要事项这些查询使用锁定提示来确保不会在关键的数据库表上获取锁,以确保这些查询不会影响正在运行的系统。此外,除了向临时表中进行插入以外,这些查询不会在数据库中更新、插入、删除或以其他方式修改数据或结构。至关重要的一点是,不要以任何方式修改这些查询以改变这些数据库中的数据或结构。最后,因为
BizTalk
Server
中的数据库结构是内部组件,所以它有可能更改,并且这些查询可能无法正常工作。将来版本的
BizTalk
Server
可能将这些查询或类似的查询合并到管理和监视工具中。
按状态分组的所有编排实例的统计
用途:对于系统中的每个编排,提供每个状态下的实例个数。通过
Administration
控制台中的属性页可以获得单个编排的这一信息,但该查询提供所有编排的汇总信息。
如何理解结果:如果有大量挂起实例,则表明某个方面出现了故障。如果有大量未得到充分响应的实例,则可能表明后端系统存在不能迅速响应的问题,或者它仅仅意味着消息处理服务所在的宿主未启动。如果有大量“准备运行”服务,则
BizTalk
服务器尚未启动,或者系统的负载较高并且系统正在试图赶上进度。如果系统稍后赶上了进度,则后一种情况可能不是问题。否则,系统中的负载可能过高,并且应该采取措施以横向扩展基础结构。
做什么:如果编排被挂起,则需要查看
HAT
中的挂起信息,确定它们被挂起的原因,并且采取纠正操作。如果编排未得到充分响应,并且有理由予以关注,则应该检查消息流,查看编排正在等待什么,然后基于停止点确定原因。如果编排准备好运行,则请确保
BizTalk
Server
服务正在运行,并且检查这些计算机中的
CPU
利用率和其他资源问题,以确定潜在的负载问题。
SET NOCOUNT ON
TRANSACTION ISOLATION LEVEL READ COMMITTED
DEADLOCK_PRIORITY LOW
o.nvcName AS Orchestration, COUNT(*) as Count,
i.nState
1 THEN 'Ready To Run'
2 THEN 'Active'
4 THEN 'Suspended Resumable'
8 THEN 'Dehydrated'
16 THEN 'Completed With Discarded Messages'
32 THEN 'Suspended Non-Resumable'
as State
[BizTalkMsgboxDb]..[Instances] AS i WITH (NOLOCK)
[BizTalkMgmtDb]..[bts_Orchestration] AS o WITH (NOLOCK) ON i.uidServiceID = o.uidGUID
--WHERE dtCreated > '2004-08-24 00:00:00' AND dtCreated < '2004-08-24 13:30:00'
BY o.nvcName, i.nState
所有宿主队列的统计
用途:该查询为所有应用程序返回三个队列的表大小:工作、挂起和状态。
如何理解结果:如果挂起队列增长,则处理正在发生故障。如果工作队列增长,则系统正在超速运转、BizTalk
服务关闭或者可能正在发生故障,但重试操作挂起。如果状态队列增长,则有大量未得到充分响应的编排,或者可有与
MSMQT
相关的问题。
做什么:如果挂起队列的大小正在增长,则请使用 HAT 中的错误信息和事件日志来查明故障。最后,需要使挂起实例继续运行或终止,以便将它们从系统中清除出去。如果是工作队列的大小正在增长,则应该检查服务以确保它们正在运行,并且应该检查
CPU 和其他资源,以验证系统是否过载。还应该检查事件日志中是否存在指示可能导致重试的传输故障的警告。如果是状态队列的大小正在增长,则请检查是否存在大量未得到充分响应的或挂起的编排,并且确定它们被挂起的原因。还可以使用后面的查询之一来尝试确定该问题是否与拥有所有引用的
MSMQT 实例有关。如果问题确实与 MSMQT 有关,则 BizTalk Server 可能不再向传出队列发送数据,因为它正在等待较早消息的确认。
SET NOCOUNT ON
TRANSACTION ISOLATION LEVEL READ COMMITTED
DEADLOCK_PRIORITY LOW
TABLE #Temp (AppQueue nvarchar(256), QueueSize
int)
@nvcAppName sysname
MyCursor CURSOR FAST_FORWARD FOR
nvcApplicationName FROM [BizTalkMsgboxDb]..[Applications]
WITH (NOLOCK)
MyCursor
NEXT FROM MyCursor INTO @nvcAppName
(@@FETCH_STATUS = 0)
INSERT INTO #Temp
exec ('SELECT ''' + @nvcAppName + 'Q'', COUNT(*) FROM ' +
@nvcAppName +'Q WITH (NOLOCK)')
INTO #Temp
('SELECT ''' + @nvcAppName + 'Q_Suspended'', COUNT(*) FROM
' + @nvcAppName +'Q_Suspended WITH (NOLOCK)')
INTO #Temp
('SELECT ''InstanceStateMessageReferences_' + @nvcAppName
+ ''', COUNT(*) FROM InstanceStateMessageReferences_' + @nvcAppName
+ ' WITH (NOLOCK)')
NEXT FROM MyCursor INTO @nvcAppName
* FROM #Temp ORDER BY QueueSize DESC
MyCursor
MyCursor
TABLE #Temp
按照实例分组的状态消息的个数统计
用途:将提供与每个实例相关联的状态消息的个数统计。
如何理解结果:这一特定查询可能只对在事情开始出错时使用 MSMQT 作为快速警告的系统有用。如果对于特定实例,有大量消息累积起来,并且该实例为
MSMQT 实例,则可能有问题。
做什么:如果有上述有问题的实例之一,并且服务类是 MSMQT,则可以查看 UserState
列以确定正在与之对话的队列的名称;然后,需要查看与该计算机的通信无法正常工作的原因。如果是编排,则这可能正是预期的结果。至于为什么有如此多的消息正在奔向同一个实例,则取决于业务逻辑。
SET NOCOUNT ON
TRANSACTION ISOLATION LEVEL READ COMMITTED
DEADLOCK_PRIORITY LOW
TABLE #Temp (AppName nvarchar(256), StateMessages
int, InstanceID uniqueidentifier, ServiceClassID uniqueidentifier,
ServiceState nvarchar(256))
@nvcAppName sysname
MyCursor CURSOR FAST_FORWARD FOR
nvcApplicationName FROM [BizTalkMsgboxDb]..[Applications]
WITH (NOLOCK)
MyCursor
NEXT FROM MyCursor INTO @nvcAppName
(@@FETCH_STATUS = 0)
INTO #Temp
('SELECT ''' + @nvcAppName + ''', COUNT(*), i.uidInstanceID,
i.uidClassID, i.nvcUserState FROM InstanceStateMessageReferences_'
+ @nvcAppName + ' AS s WITH (NOLOCK) JOIN Instances AS i WITH
(NOLOCK) ON s.uidInstanceID = i.uidInstanceID GROUP BY i.uidInstanceID,
i.uidClassID, i.nvcUserState')
NEXT FROM MyCursor INTO @nvcAppName
* FROM #Temp ORDER BY StateMessages DESC
MyCursor
MyCursor
TABLE #Temp
特定发送端口的所有活动消息的统计
用途:指示给定发送端口的工作队列中有多少个消息,并且提供有关这些消息是由主要传输器还是辅助传输器发送的信息,以及针对该消息所尝试的重试操作的个数。
如何理解结果:对于特定的传输器存在较高数量的实例未必表明存在问题,并且可能只是表明系统在处理该传输器的消息方面存在积压现象。但是,如果重试计数较高,则表明正在发生发送故障。具有较高数量的使用辅助传输器的消息表明,主要传输器正在发生故障(即使借助了重试操作),并且处理已经移动到辅助传输器。
做什么:检查事件日志中是否存在有关正在发生故障的原因的信息,并且尽可能验证该发送端口与有关系统或应用程序之间的通信,以便确定问题所在。
SET NOCOUNT ON
TRANSACTION ISOLATION LEVEL READ COMMITTED
DEADLOCK_PRIORITY LOW
TABLE #Temp (MaxRetries int, Active int, SendPort nvarchar(256),
IsPrimaryTransport int)
@nvcAppName sysname
MyCursor CURSOR FAST_FORWARD FOR
nvcApplicationName FROM [BizTalkMsgboxDb]..[Applications]
WITH (NOLOCK)
MyCursor
NEXT FROM MyCursor INTO @nvcAppName
(@@FETCH_STATUS = 0)
INTO #Temp
('SELECT MAX(q.nRetryCount) AS MaxRetries
,COUNT(*) AS Active
,sp.nvcName AS SendHandlersHostServiceName
,spt.bIsPrimary AS IsPrimaryTransport
' + @nvcAppName + 'Q as q WITH (NOLOCK)
INNER LOOP JOIN Subscription as s WITH (NOLOCK) ON q.uidServiceID
= s.uidServiceID AND s.uidPortID = q.uidPortID
INNER LOOP JOIN [BizTalkMgmtDb]..[bts_sendport] as sp WITH
(NOLOCK) ON q.uidServiceID = sp.uidGUID
INNER LOOP JOIN [BizTalkMgmtDb]..[bts_sendport_transport]
as spt WITH (NOLOCK) ON sp.nID = spt.nSendPortID AND spt.uidGUID
= q.uidPortID
BY sp.nvcName, s.uidPortID, spt.bIsPrimary
BY SendHandlersHostServiceName ASC')
NEXT FROM MyCursor INTO @nvcAppName
* FROM #Temp ORDER BY Active DESC
MyCursor
MyCursor
TABLE #Temp
SQL Agent 作业的上一次持续时间
用途:该查询提供有关每个 BizTalk Server 作业上一次运行的时刻和持续时间的信息。监视该信息很重要,目的是确保这些作业正在正确运行,正是因为它们对于良好的系统性能至关重要才让它们运行的。
如何理解结果:如果作业开始花费大量的时间,则可能存在数据库资源问题。这些问题可能与
CPU 性能或较高的磁盘活动性有关。如果作业失败,则可能是在 BizTalk Server Service Pack
1 中修复的很多死锁问题。
做什么:如果是资源问题(如 CPU 或磁盘问题),则可能需要纵向扩展 BizTalk
Server 以添加处理器或更快的磁盘。如果故障只是偶尔发生,则可能不是太大的问题,因为作业可以重新运行(只是有更多的工作需要完成)。如果服务器尚未应用
Service Pack 1,则应该应用它,因为它包含很多有价值的修复程序。
SET NOCOUNT ON
TRANSACTION ISOLATION LEVEL READ COMMITTED
DEADLOCK_PRIORITY LOW
TABLE #Temp (JobID uniqueidentifier,
JobName sysname,
Status int,
DateStarted int,
TimeStarted int,
Duration int,
OpEmailed nvarchar(20),
OpNetSent nvarchar(20),
OpPaged nvarchar(20),
Retries int,
Server nvarchar(30)
)
TABLE #Final (MessageBox sysname,
JobName sysname,
DateStarted int,
TimeStarted int,
Duration int,
Status int
)
@dbName sysname
MyCursor cursor FAST_FORWARD FOR
DBName FROM [BizTalkMgmtDb]..[adm_MessageBox] WITH (NOLOCK)
MyCursor
NEXT FROM MyCursor INTO @dbName
(@@FETCH_STATUS = 0)
INSERT INTO #Temp
exec ('[msdb]..[sp_help_jobhistory] @job_name = ''MessageBox_DeadProcesses_Cleanup_'
+ @dbName + '''')
INSERT INTO #Final
SELECT TOP 1 @dbName, JobName, DateStarted, TimeStarted, Duration,
Status FROM #Temp
TRUNCATE TABLE #Temp
INSERT INTO #Temp
exec ('[msdb]..[sp_help_jobhistory] @job_name = ''MessageBox_Message_Cleanup_'
+ @dbName + '''')
INSERT INTO #Final
SELECT TOP 1 @dbName, JobName, DateStarted, TimeStarted, Duration,
Status FROM #Temp
TRUNCATE TABLE #Temp
INSERT INTO #Temp
exec ('[msdb]..[sp_help_jobhistory] @job_name = ''MessageBox_Parts_Cleanup_'
+ @dbName + '''')
INSERT INTO #Final
SELECT TOP 1 @dbName, JobName, DateStarted, TimeStarted, Duration,
Status FROM #Temp
TRUNCATE TABLE #Temp
INSERT INTO #Temp
exec ('[msdb]..[sp_help_jobhistory] @job_name = ''PurgeSubscriptionsJob_'
+ @dbName + '''')
INSERT INTO #Final
SELECT TOP 1 @dbName, JobName, DateStarted, TimeStarted, Duration,
Status FROM #Temp
TRUNCATE TABLE #Temp
INSERT INTO #Temp
exec ('[msdb]..[sp_help_jobhistory] @job_name = ''TrackedMessages_Copy_'
+ @dbName + '''')
INSERT INTO #Final
SELECT TOP 1 @dbName, JobName, DateStarted, TimeStarted, Duration,
Status FROM #Temp
TRUNCATE TABLE #Temp
FETCH NEXT FROM MyCursor INTO @dbName
* FROM #Final ORDER BY MessageBox
MyCursor
MyCursor
table #Temp
table #Final
|