深入biztalk消息以及消息订阅发布路由机制
【本文转自:http://www.cnblogs.com/chnking】
一. 消息概述
BizTalk Server 基于消息发布/订阅的基本机制,在BizTalk Server内部分为发布服务器和订阅服务器,所谓发布服务器就是产生消息并发布到MessageBox中,所谓订阅服务器就是可以消费消息的服务器,根据订阅的消息的条件,只对自己需要的信息进行处理。
一般的情况是从外部收到的信息经过接收端口后都会被处理转换包装成biztalk的消息,消息中包含了相关属性(消息上下文),比如接收的端口、接收的适配器信息,还有预设的从消息内部提取出来的可以用来路由消息的特征值(从schema升级的属性)。把然后存放到消息数据库MessageBox中。
消息代理根据订阅表查找订阅了此消息的服务,并将订阅了此消息的所有订阅分别存到的相应的服务器实例对应的消息队列表中,系统不断的轮询此队列,发现有未处理的消息,就会把消息发送到订阅此消息的服务,同时把此条订阅消息从消息队列表去除。
订阅消息的服务(这个服务可能是业务流程,也可能是输出端口等等)收到消息后对消息就行处理,处理完后,如果服务是输出端口,此消息就经过适配器发送到系统外部。如果服务是业务流程,可能会产生新的消息,这种情况的处理同从接收端口进来的消息做类似的处理,发送消息到MessageBox,并查找订阅此消息的服务,把消息发送到订阅消息队列中继续流转。
1. 发布和订阅服务器
发布服务器指的是可以送出消息发布到MessageBox的服务,包括:
接收端口 ―― 从外部接收信息,处理后发布到MessageBox
业务流程 ―― 业务流程启动后可能生成消息发送到发送端口或启动别的业务流程,生成的消息也是发布到MessageBox
要求/响应(Solicit-Response)发送端口 ―― 即双向的要求/响应发送端口。
订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类:
XLANG/s – 业务流程(orchestration)
Messaging InProcess – 表示发送端口,Solicit- Response发送端口
MSMQT – MS消息队列
Messaging Isolated – 基本上就是指HTTP和SOAP这类需要外部IIS这类进程接收消息的Request-Response双向接收端口。
Biztalk中的发布和订阅都是相对MeassagBox而言的,导致消息进入到MeassagBox的就叫发布,根据条件从MeassagBox中取得消息的叫订阅。
2. 消息构成
BizTalk Server 2006 本质上是一个消息处理引擎。每条消息都可视为多部分消息,此消息可以由零个或多个部分组成。具有一个或多个部分的消息都具有一个标识为正文部分的部分(只能有个一部分标识为正文)。每个部分均由可表示 XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块组成。消息的正文部分的类型决定了整个消息的类型,这个消息类型可用为路由消息的条件。

2.1. 上下文
每条消息都附带有一个属性集,称为消息上下文,这些属性的值是从消息本身提取或不来自消息本身但与消息本身相关的值。属性分为两种类型,升级属性和写入属性。这两种属性的区别是升级的属性可以用来作为路由消息的条件,写入的属性不能。
2.1.1. 升级属性
要把一个消息架构中的某个元素升级到上下文属性,首先要有一个属性架构来对应各个被升级的属性。这个属性架构是简化的架构,只能包含简单数据类型的元素,每个元素对应一个属性,并分配给这个属性一个GUID,用来标识这个属性。看一下升级属性架构中的一个属性:
<xs:element name=”Property
<xs:annotation>
<xs:appinfo>
<b:fieldInfo propertyGuid=”4bfe2aee
</xs:appinfo>
</xs:annotation>
</xs:element>
属性名是Property1,属性GUID是4bfe2aee
如果接收管道中使用了xml拆装器,则消息经过接收端口的管道时,xml拆装器会自动把入站的消息跟已部署的消息架构匹配,匹配到合适的消息架构后,管道根据消息架构中升级属性的xpath提取相应的值保存到上下文属性,然后消息代理把这些属性的GUID和值保存到BizTalkMsgBoxDb数据库MessageProps 表中,就可以根据订阅的条件对照这些属性来路由消息。
比如某个服务订阅了消息类型为订单、收货单位为xxxx的消息,这里消息类型和收货单位都是升级到上下文属性的属性,消息代理发现这个条件跟收到的消息一致就会把消息发送到这个订阅的服务。
升级的属性除了从消息本身升级来的外,还有不是消息本身的信息但跟此消息相关的系统属性,比如接到此消息的端口适配器类型,接收此消息的端口id等等,这些系统属性也都具有自己的guid,会同其他的升级属性一同写入到MessageProps 表中作为路由消息的依据。
关于详细的消息订阅和消息发布路由的机制下面有专门章节论述。
2.1.2. 写入属性
写入属性就是把消息架构中的某个元素设置为可分辨字段(Distinguished),消息架构会保存此写入属性在架构中位置信息的xpath,以便可以通过xpath提取元素的值。
写入的属性不必有属性架构的支持,也就没有相应的GUID,属性会被提取出来保存到上下文属性中,但是写入的属性不能用来作为路由消息的条件。
写入属性一般是为了在业务流程中方便的访问消息中的某个元素,可以直接从消息上下文属性获得此属性值,省去开发人员手工解析提取这个元素的值麻烦。
2.2. 多部分
biztalk中的消息又叫做多部分消息,意为一个消息可以包含多个部分,每个部分都可能为XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块。一个消息可以是简单的只包含一个部分的消息,也可以有多个部分,但是其中必须并且只能有一个部分为正文本分,正文部分的类型就是整个消息的消息类型。
3. 消息流程
先看一下消息在biztalk中的流转过程的图,下面详细描述一个从接收端口进入到biztalk后一直到消息发送出biztalk的整个过程。

3.1. 接收端口
一个接收端口可以包含多个接收位置,每个接收位置就是一个对外接收信息的实际物理接口。接收位置接收到的信息经过处理,如果设计有映射,经过消息映射转化后再由消息代理根据消息订阅情况分发到各个订阅消息的服务。
接收位置:
接收位置主要由接收适配器和接收管道组成,这两个组件再终结点管理器的管理下协调工作。
接收适配器:
Biztalk作为EAI和B2B的平台软件,需要跟各种系统通讯联系,所以信息接口应该覆盖面尽可能的广,能够接收各种协议的各种类型的信息,biztalk使用适配器来解决这个问题,通过适配器来把多种多样的信息接收进来并转换成biztalk内部使用的xml的消息。
Biztalk本身包含了许多一般常用的适配器SOAP、http、ftp、Smtp、平面文件、msmq、sql等等,基本能满足大多数需求。如果biztalk本身带的适配器不能满足需求,还可以自己开发需要的适配器,另外有很多第三方的公司也提供各种自己开发的适配器给需要者使用。
接收适配器读取数据流创建消息(Microsoft.BizTalk.Message.Interop.IbaseMessage 接口的实现)、向该消息添加部分(Microsoft.BizTalk.Message.Interop.IbasePart 接口的实现)、然后将数据流作为该部分内容进行提供。
接收消息后,适配器将接收位置、适配器类型以及其他内容(与适配器相关)升级到消息的上下文属性中, 以便根据接收位置或适配器类型订阅消息的服务能接收到订阅的消息。
接收管道:
从适配器出来的消息在终结点管理器的控制下送到接收管道中进行处理。
接收管道如上图包含四个阶段的处理,这里我们重点关心的是拆装部分,biztalk提供了两个拆装器:xml拆装器和平面文件拆装器。
Xml拆装器可以指定消息的架构,xml拆装器就会按照这个消息的架构对消息就行处理,并可以校验消息是否符合架构。同时如果消息架构中有升级属性,这一步会根据升级属性的xpath把属性值从消息中提取出来放到消息的上下文属性中。指定了消息架构的xml拆装器还会把消息的类型升级到消息的上下文属性中,消息的类型为命名空间(后接 # 符号)和根节点的名称组成。比如:http://tempuri.org/samples/MessageType#Message
平面文件拆装器需要指定架构,并且架构是做过平面文件扩展的,就是架构中包含如果分解平面文件数据到xml的信息,平面文件拆装器根据这些信息解析平面文件,并生成xml。
Biztalk的默认接收管道PassThruTransmit是个直通的管道,它不处理xml文档,所以如果要接收xml消息并升级属性此管道不适合。
Biztalk的默认接收管道XMLReceive,这个管道专门用来拆装xml消息,管道中的xml拆装器根据架构的命名空间和根节点名称在已经部署的所有架构逐个进行匹配,一旦匹配成功,就使用这个架构,然后根据schema中升级属性的xpath提取属性值放入上下文。如果一个都没匹配成功,则根据是否路由不可识别消息的设置确定是否继续路由,还是生成错误信息。
消息代理:
接收管道出来的消息又回到终结点管理器,如果有映射器就把消息送到映射器就行消息的架构转换,如果没有把消息直接送到消息代理,由消息代理把消息发布到MessageBox中,并处理消息的路由。
消息的发布和路由在下面章节详述。
3.2. MessageBox
Microsoft BizTalk Server 中发布/订阅引擎的核心是 MessageBox 数据库。接收到的消息存放在MessageBox中,各个服务消息订阅也是存放在MessageBox中,发送到订阅服务的消息队列也是在MessageBox中。MessageBox是biztalk的信息核心。
3.3. 业务流程
业务流程可以参与消息的处理过程,但业务流程不是处理消息过程的必须过程,一个消息流程完全可以直接从接收端口到MessageBox,然后直接路由到一个输出端口,不需要业务流程的参与。
业务流程中也有端口概念,是业务流程内部跟外部进行消息交换的接口,业务流程的端口称作逻辑端口,用户从biztalk外部接收数据、发送数据的端口称作物理端口,是不同的概念。
业务流程可以作为发布服务器也可以作为订阅服务器,作为订阅服务器时可以订阅从物理接收端口进入biztalk的消息,作为发布服务器,业务流程从逻辑输出端口输出的消息也可以被别的订阅服务器订阅。
业务流程内部的各种功能不在本文的讨论范围,可以自行参考biztalk的随即文档的业务流程相关的部分。
3.4. 发送端口
在消息准备从 BizTalk Server 发送时,它将在发送端口中经历一个互补的过程。映射将在发送管道执行前应用于消息,从而,消息在由管道处理并通过适配器发送前转换为特定于客户或应用程序的格式。在发送管道中,属性将从上下文降级到消息中,而非升级到消息上下文中。
二. 消息订阅
订阅消息的主体叫订阅服务器,订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类,在BizTalkMgmtDb管理数据库中的adm_ServiceClass的Name字段列出了所有可以作为订阅服务器的服务类型,包括:
XLANG/s – 业务流程(orchestration)
Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口
MSMQT – MS消息队列
Messaging Isolated –基本上就是指HTTP和SOAP这类需要外部IIS这类进程接收消息的Request-Response双向接收端口。
每种服务都有自己的id,为16字节GUID的形式(biztalk中对象的标识id都采用GUID的形式,biztalk数据表中大量的使用到这种16字节的统一标识数据,sql server中的uniqueidentifier 数据类型,就是用来存放GUID类型的数据)
1. 消息订阅主体
消息订阅最后体现在BizTalkMsgBoxDb数据库的反应订阅主体的Subscription表(表示是哪个服务产生的这个订阅)和反应订阅条件的一组谓词表中(表示这个订阅的具体条件,用来判断哪些消息是符合这个订阅的)。
先来看一下Subscription表的主要字段含义,这个表指示了订阅消息的具体是哪一个服务:
nvcName ―― 此订阅的名称
uidSubID ―― Guid类型,此订阅的uid
nvcApplicationName ―― 订阅服务器所属主机实例名,路由消息时不同的主机实例将调用不同的存储过程处理。
uidClassID ―― 产生订阅的服务类型,是adm_ServiceClass 服务表UniqueId字段的外键
uidServiceID ―― 产生此订阅的具体服务,根据uidClassID服务类型的不同,对应到不同类别的服务.如果uidClassID指示的是XLANG/s,则对应的是业务流程,此字段对应到bts_Orchestration表中的uidGUID字段。为Messaging In-Proc时,表示由发送端口订阅消息,对应到bts_SendPort表中的uidGUID 。MSMQt时有点混乱,但大多数情况下也在bts_SendPort table表中体现。实例订阅时,服务实例是请求/响应端口时的这个字段并不是接收端口的guid也不是接收位置的guid,不清楚这个guid跟什么对应。哪位知道的朋友能不能告知一下。
uidInstanceID ―― 实例订阅时的订阅消息的服务实例的guid。激活订阅时此字节为空。
Biztalk中存在两类订阅:激活订阅和实例订阅。激活订阅收到执行订阅的消息后,该消息创建新的订阅服务器实例来处理这个消息,一般的订阅都属于这类订阅。实例订阅将指示执行订阅的消息应路由到已在运行的订阅服务器实例。有一种服务会产生实例订阅的情况即“Messaging Isolated ”,这类服务的典型就是“请求/响应接收端口”,这类服务其实可以产生两个订阅。先是“请求/响应接收端口”的接收部分,这部分相当于消息分布服务器,可以由其他订阅服务器来订阅这个消息,比如一般是业务流程的一个接收形状绑定这个端口,实际上就是订阅了这个端口接收到的消息。这个接收端口服务实例建立后,把收到的消息发送到MessageBox。然后“请求/响应接收端口”的响应部分,这部分相当于订阅服务器,如果这部分绑定到业务流程的一个发送形状,这个接收端口服务实例会生成一个实例订阅,这个字段就用来存放这个接收端口服务实例的guid,表示还是由这个服务实例来处理返回的消息。这样,就完成了一个从接收到响应的一个往返过程。实例订阅是由服务实例生成的订阅,所以在产生实例订阅收到消息被放入到消息队列后,会在订阅表中把这个实例订阅删除。
还有一种情况会形成实例订阅,orchestration中使用相关集时,一个orchestration实例的一个端口发送消息出去,返回的消息仍然由这个orchestration实例的一个接收形状接收。这里也有一个orchestration实例的订阅发出去的消息的实例订阅。
uidPortID ―― 订阅服务类型为XLANG/s时,此字段表示业务流程中接收消息的端口形状,对应bts_orchestration_port表中的uidGUID字段。对于Messaging或MSMQt,这个字段指示使用发送端口的主通道还是辅助通道。先根据uidServiceID在bts_SendPort表中找到相应的端口,然后用bts_SendPort表的nID跟bts_sendport_transport表的nSendPortID 关联。uidPortID跟bts_sendport_transport表的uidGUID 关联,就能确定使用哪个发送端口的通道。
fEnabled ―― 此订阅的状态,0 ―― 禁用;1 ―― 活动;2 ―― 停用
uidPredicateGroupID ―― 订阅谓词组id。
2. 消息订阅条件(谓词)
一个订阅的主体存在Subscription表中,相应的订阅条件则分别存在一组谓词表中,不同的类型的条件放在不同的表中,比如等于的条件放在EqualsPredicates中,小于的条件放在LessThenPredicates中,一共有以下这么多的谓词表:
l BitwiseANDPredicates
l EqualsPredicates
l EqualsPredicates2ndPass
l ExistsPredicates
l FirstPassPredicates
l GreaterThanOrEqualsPredicates
l GreaterThanPredicates
l LessThenOrEqualsPredicates
l LessThenPredicates
l NotEqualsPredicates
这些表的基本结构都是一样的,都是按照什么属性(属性的GUID)+谓词(等于、大于、小于。。。)+属性的值的形式出现。看一下这些表的数据结构:
uidPropID ―― 属性的guid。所有属性都有自己的guid,包括类似接收端口、接收适配器类型等系统属性,或者从schema升级的属性。
vtValue ―― 属性的值
uidPredicateGroupID ―― 一组谓词的id,一个订阅可以有多个条件谓词,这个字段表示同一组的条件,条件也可分为或条件和与条件,条件的组合通过PredicateGroup表在Subscription表和各个谓词表之间进行组合。
看一下PredicateGroup表的结构:
uidPredicateORGroupID -- 订阅谓词,对应到Subscription的uidPredicateGroupID字段表示是与这个订阅相关的条件。此字段相同的记录表示相应的订阅有多组条件用或运算组成。
uidPredicateANDGroupID -- 订阅谓词与运算一组的谓词id,每个或运算条件都能由多条与运算的条件组成,最终由这个字段跟各个谓词表去关联。
nNumFirstPassPredicates -- 这个或条件组中包含几个与运算条件。
这个图中显示了一个发送端口设置筛选条件(就是设置订阅条件)的示例。从上可以看出这个订阅实际上设置了两组或运算的条件,其中第一组内又包含了两个与运算的条件。在PredicateGroup表中的表现就是对应这个订阅的有两条uidPredicateORGroupID相同的记录,表示两组或运算条件,相应的uidPredicateANDGroupID字段对应到EqualsPredicates谓词表中,其中有一个对应两条记录,一个对应一条记录。
3. 消息订阅过程
下面的图是涉及到订阅部分数据库表的主要结构和相互关系。各字段的含义上面两节基本都有描述了。
下面看一下biztalk中两种订阅方式的各自如何产生订阅的:
3.1. 激活订阅
一般情况下,业务流程、发送端口和MSMQ这三类服务产生的订阅是激活订阅。这种订阅是被订阅的消息根据订阅条件新建订阅消息的服务主体,即新建一个服务实例,然后由这个服务实例处理订阅的消息,服务实例将消息处理完毕后,这个实例就算完成任务。
业务流程订阅一般表现形式是业务流程的接收端口跟物理端口绑定。其实它的订阅主体是这个业务流程的这个接收端口。条件是绑定的发送端口发送来的消息。
发送端口订阅一般可以是发送端口绑定到业务流程的一个发送端口。也可以是发送端口直接订阅由接收位置接收的消息,在发送端口的筛选器中可以根据消息的各种属性(包括系统属性和从schema升级的属性)来定义订阅的条件,比如某个消息的哪个升级属性大于某个值时。
以业务流程绑定一个物理接收端口为例说明:
业务流程跟一个物理接收端口绑定后,实际进行的操作是产生一个订阅。就是在Subscription表中生成一条记录表示订阅的主体,然后在一组谓词表中生成相应的订阅条件。
订阅主体就是这个业务流程的绑定的端口。在Subscription表中生成记录,字段uidClassID是XLANG/s业务流程服务的guid,表示订阅主体的服务类型是业务流程。uidServiceID字段是订阅的具体业务流程的guid(对应bts_Orchestration表)。uidPortID字段是这个业务流程中接收端口的guid(对应bts_orchestration_port表),uidPredicateGroupID字段是订阅谓词组id,表示跟这个订阅相关的谓词表中的记录。
订阅主体在Subscription表生成记录后,然后把订阅的条件要记入到相应的谓词表中。本例中端口绑定的情况,会产生两个条件,一个是接收端口id等于绑定的那个接收端口,形式为:http://schemas.microsoft.com/BizTalk/2003/system-properties. ReceivePortID == {9DB
这两个条件中都含有“system-properties”,表示是系统属性,这两个又都是等于条件,所以都记入EqualsPredicates谓词表中。所有的属性都有guid,系统属性有预设的guid,升级属性前面章节已经讲到,每个升级的属性都会生成一个guid(这就是为什么升级的字段可以用来路由消息,而可分辨字段不可以用来路由消息的原因,可分辨字段没有guid)。所以这两个条件在谓词表中的表现是,两属性guid代表的属性分别等于什么值。
3.2. 实例订阅
这种订阅由服务实例产生,并放入到订阅表中。当消息路由到消息队列后,会将订阅表中的实例订阅记录删除。
一般来讲Messaging Isolated(表示物理请求/响应类型的接收端口)这类服务会用到实例订阅,下面就这个为例说明:
请求/响应类型的接收端口,一般是由业务流程订阅这个端口的请求部分的消息,经过处理后返回消息,这个端口的的响应部分再订阅这个返回的消息,处理后返回给请求端。
请求部分的订阅是激活订阅,不再赘述。
响应部分是实例订阅,是由请求部分收到消息后新建Messaging Isolated服务实例后,这个服务实例在订阅表中建立的一个订阅,这个订阅其他部分跟激活订阅一致,就是在Subscription表中的uidInstanceID字段填入这个服务实例自身的guid,表示这是实例订阅,订阅主体就是这个服务实例。
当消息路由到这个实例订阅的消息队列后,这个实例订阅的使命就完成了,被从订阅表中删除,这个过程在下面消息的发布和路由中还有叙述。
4. 端口绑定的本质
端口绑定本质就是生成订阅,订阅服务器订阅发布服务器的消息,但是不表示这个发布服务器的消息只能给一个订阅服务器消费,一个消息可以同时被多个服务订阅,只要这个消息符合订阅条件,消息代理会把订阅把消息发送到所有订阅这个消息的服务实例。
三. 消息发布和路由
消息的发布有几种情况,上面讲述发布服务器时说过的三种发布服务都可以发布消息,他们的发布原理基本一致,这里以适配器发布消息为主进行描述消息发布和路由的过程。
上面的消息流程章节中描述了消息从接收端口接收进入biztalk后的处理过程,但是消息如何进入到MessageBox,如何路由到订阅此消息的服务的过程还没详细说明,这里将详细描述消息的发布和路由过程。
消息代理处理整个消息发布和路由的过程。
消息在经过了接收管道处理后,相关的属性已经升级到消息上下文(使用了默认接收管道XMLReceive或自建的管道中使用了xml拆装器的情况下biztalk会根据schema升级属性,使用默认接收管道PassThruTransmit不会升级属性)。
消息代理获得消息后,先把消息的属性写入到MessageBox数据库的消息属性表MessageProps表,包括了此消息的系统属性和从消息本身升级的属性。
MessageProps表的结构如下:
uidBatchID -- 消息批次id。此字段还不是很清楚,可能是一次可能处理多个消息,同一批进行处理的作为一个批次。
uidMessageID -- 消息的uid
uidPropID -- 这个消息的某一个属性,属性都用guid来表示。
vtPropValue -- 这个属性的值
1. 调用bts_FindSubscriptions过程
消息代理调用bts_FindSubscriptions存储过程来查找订阅了消息的相关订阅。这个存储过程把订阅表和各个订阅谓词表定义的订阅跟消息的属性表中的属性比较,找出所有所有符合此消息条件的订阅,返回到一个结果集中。这个结果集中最主要包含字段有:订阅guid(uidSubID)、此订阅所属主机实例名(nvcApplicationName)、消息guid(uidMessageID),服务的guid(uidServiceID)、服务实例的guid(uidInstanceID),结果集表示订阅表中的哪些订阅匹配到了这个消息,每个订阅了此消息的订阅为一条记录。如果一个消息被两个服务订阅了,这个结果集中将会有两条记录。
消息代理根据这个结果集,对每条记录调用存储过程bts_InsertMessage。以下的步骤,结果集中的每条记录都要执行一遍。
2. 第一次调用bts_InsertMessage过程
这次调用bts_InsertMessage过程主要任务是调用int_EvaluateSubscription过程。int_EvaluateSubscription过程根据当前处理的订阅所属的应用主机实例名,再调用对应此主机实例的存储过程进一步处理,调用的存储过程名称是“int_InsertIntoQueue_主机实例名”这样的命名形式,比如服务属于BizTalkServerApplication主机,则调用int_InsertIntoQueue_BizTalkServerApplication。
存储过程“int_InsertIntoQueue_主机实例名”的作用:
2.1. 激活订阅时,新建实例guid
如果是订阅类型是激活订阅,即uidInstanceID为空,说明需要新建一个处理订阅消息的服务实例,这一步骤新建一个guid,作为新建实例的实例guid。
之后要新建实例记录,即在实例表中插入一条实例记录表示要处理这条消息的实例。
Instances实例表主要字段:
|
主要字段 |
含义 |
|
uidAppOwnerID |
所属主机实例的guid |
|
uidInstanceID |
此服务实例的guid |
|
uidServiceID |
此服务的guid |
|
uidClassID |
此服务的类型guid |
|
uidProcessID |
运行这个实例的线程guid |
|
nState |
实例状态: |
2.1.1. 订阅状态为活动时(状态码为1)
如果订阅的状态是处于正常的活动状态,则在Instances实例表中插入一条记录,只记入四个字段:uidAppOwnerID, uidInstanceID, uidServiceID, uidClassID, nState。uidAppOwnerID为此主机实例的guid,uidInstanceID为新建的服务实例的guid,uidServiceID为订阅消息的服务guid,uidClassID为订阅服务的类型guid,nState实例状态为256,表示已计划要运行。
2.1.2. 订阅状态为停用时(状态码为2)
如果订阅的状态是处于停用状态,在插入Instances实例表(插入的状态字段nState为4)的同时还会在InstancesSuspended挂起实例表中都插入一条记录,挂起实例表中除了上面上面提到的四个字段,还包括一些错误代码和错误描述字段,可能是描述挂起原因的一些内容。此时nState实例状态为4,表示这个实例是可恢复的挂起。
2.1.3. 订阅状态码为8时
我没看出来这个状态码的含义,有知道的朋友请告知
如果是实例订阅,由于订阅的主体服务实例已经存在(在实例表中有这个服务的一条记录),不必再新建一个订阅服务的实例。
2.2. 将消息队列插入到相应的主机队列表
如果订阅是处于活动状态,这一步骤要将这个订阅内容插入到相应的主机队列表中,主机队列表为“<主机实例名>Q”形式,表示路由到此主机的还未被处理的消息队列。如果相应的主机实例是BizTalkServerApplication,那就将此消息订阅内容插入到BizTalkServerApplicationQ表中。
主机队列表BizTalkServerApplicationQ主要字段:
|
主要字段 |
含义 |
|
nID |
自动标识 |
|
uidMessageID |
消息guid |
|
uidSubscriptionID |
订阅guid |
|
uidClassID |
订阅此消息的服务类别guid |
|
uidServiceID |
订阅此消息的服务guid |
|
uidInstanceID |
订阅此消息的服务实例guid |
|
uidAppInstanceID |
主机实例guid |
如果订阅处于停用(订阅状态码为2),或者订阅状态码为8(此码含义不清楚)时,订阅内容不被插入到主机队列表中,而是插入到主机队列挂起表中,表示订阅此消息的订阅不处于活动状态,此消息被挂起。挂起表为“<主机实例名>Q_Suspended”形式,此表大部分内容跟主机队列表相同,增加了一些表示为何挂起的字段,比如挂起原因nvcAdditionalInfo字段,挂起是否可恢复nIsResumable字段等。
3. 其后调用bts_InsertMessage过程
接下来的过程将把消息的所有内容写入到MessageBox数据库中,一条消息本身内容在数据只有一个副本,所有订阅了此消息的服务都参考引用这一个消息的副本。消息本身是不能修改的,如果哪个服务需要修改消息,必然是需要生成另外一条消息。
3.1. 写入消息上下文和多部分消息的正文部分
其后的第一次调用bts_InsertMessage过程将传递消息上下文,然后将该消息上下文以及有关消息的元数据(例如,部分数、正文部分名称和 ID)插入到 SPOOL 表中。SPOOL 表是对一条消息的总体性描述和消息的上下文属性,一条消息在此表中有一条记录。另外,消息正文部分也在这一步使用 int_InsertPart 存储过程插入到 PARTS表中,一条消息的多部分中只能有一个正文部分。
SPOOL 表主要字段:
|
主要字段 |
含义 |
|
uidMessageID |
消息的guid |
|
UserName |
用户名,运行主机实例的用户 |
|
PublishingServer |
biztalk所在服务器的名称 |
|
OriginatorSID |
创建者的安全id,即启动biztalk服务的帐户 |
|
OriginatorPID |
创建者参与方id,一般是“s- |
|
nvcMessageType |
消息的类型,由正文部分决定的 |
|
nNumParts |
消息包含的部分数 |
|
uidBodyPartID |
正文部分的guid |
|
nvcBodyPartName |
正文部分名 |
|
nCounter |
部分计数 |
|
imgContext |
上下文的内容 |
MessageParts表是SPOOL消息队列表跟PARTS消息部分表之间的中间关系表,MessageParts. uidMessageID字段跟SPOOL.uidMessageID字段关联,PARTS.uidPartID字段跟MessagePart. uidPartID段关联。
|
主要字段 |
含义 |
|
uidMessageID |
消息guid |
|
uidPartID |
消息部分的guid |
|
nvcPartName |
消息部分名 |
|
nBodyPart |
是否正文 |
PARTS 表存放多部分消息的各个部分,一个部分在此表中占一条记录。哪一个是正文部分由spool表中的uidBodyPartID标识。
|
uidPartID |
消息部分guid |
|
nPartSize |
部分的尺寸 |
|
imgPart |
部分的数据 |
实例表Instances表跟消息队列SPOOL表之间关系,就是哪个服务实例订阅了哪个消息是靠主机队列BizTalkServerApplicationQ表的关联起来,Instances.uidInstanceID字段跟BizTalkServerApplicationQ. uidInstanceID字段相关,BizTalkServerApplicationQ. uidMessageID字段跟SPOOL.uidMessageID字段相关。
这一步的最后还有做一件事就是把消息代理写入到MessageBox属性表MessageProps中这个批次的所有属性记录都删除,因为它们已经不再有用。
3.2. 依次写入多部分消息的其他部分
随后,除了消息正文部分为每个剩余的消息部分调用 bts_InsertMessage 存储过程,把这些消息部分一一写入到PARTS 表中。
4. 消息发布和路由总结
一个消息有从适配器进来,到消息代理把消息路由到MessageBox数据口中相应的表中,这个过程就是消息的发布和路由过程。
路由完毕后,在主机队列表中存在待处理的消息队列,这个表可以简单的理解为哪个消息将要由哪个服务实例处理。
实例表Instances表存放要处理这些消息的服务实例记录,一个待处理的消息对应一个实例,实例表每条记录可以简单的理解为这个实例是哪个服务类型的服务,服务目前是什么状态。其实Instances表中的实例还需要由具体的服务线程来运行它,这时才是真正的服务实例化,这点下一章“消息的轮询和执行”中会有详细讲解。
消息的具体内容存放在SPOOL 表和PARTS 表中,Spool表存放消息的总体性描述和消息的上下文属性,PARTS 表存放消息的各个部分。
四. 消息的轮询和执行
1. 轮询机制
消息路由到MessageBox数据库中,只是在数据库中写入了相关记录,表示哪个消息需要由哪个服务实例去执行,并没有付诸实施,还需要在进程中实实在在的去实例化这个服务对象,运行服务实例对象,并把消息交给这个实例对象处理。
轮询主机队列是由订阅服务器的那些类先实例化为对象后,由服务实例去查询主机队列中的消息队列,找到是自己订阅的消息就拿过来处理,否则继续轮询下去。
服务实例轮询的时间间隔和同时运行的线程数由在BizTalkMgmtDb管理数据库中的adm_ServiceClass表中设置,此表中有两个字段:
MaxReceiveInterval ―― 设置此类服务连续轮询数据库的时间间隔
MaxDequeueThread ―― 最大出列线程的数目
adm_ServiceClass表中目前有四条记录,表示有四类可用的订阅服务类别。前面章节已经有介绍,分别是:
XLANG/s – 业务流程(orchestration)
Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口
MSMQT – MS消息队列
Messaging Isolated –表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。
每个服务相应的MaxReceiveInterval字段表示此类服务轮询主机队列的时间间隔(单位毫秒,默认轮询间隔都是500毫秒),就是说每隔这么多时间就要新开一个线程实例化一个这个服务类,这个实例去查询主机队列。
MaxDequeueThread字段表示同时处理本类服务的线程数,就是可以同时生成多少个本类服务的实例,去查询主机队列(默认都是5个)。这些实例去查询主机队列时有锁定机制进行保障,保证具体某一个时刻只能有一个实例对主机队列就行操作。
2. 轮询过程
2.1. 产生轮询的服务实例
一类服务的轮询包括两种情况,一种是激活订阅的情况,一种是实例订阅的情况。
激活订阅的情况,Biztalk先按照adm_ServiceClass表中规定的某一服务类的轮询间隔时间一次性生成MaxDequeueThread字段规定的多少个本类服务实例,就是新建多个线程,每个线程把某个服务类实例化为对象,由这些对象去查询主机队列。 这类实例是用来查询激活订阅的,因为激活订阅需要有新的实例来运行接收消息。
实例订阅的情况,原先“请求/响应接收端口”服务实例在接收消息发并发布出去后,本身会产生一个对返回消息的订阅,这就是实例订阅的情况。这类服务实例也会对主机队列进行轮询,查看是返回消息是否已经路由过来了。
2.2. 查询主机队列中的消息
2.2.1. 激活订阅的实例
每个新生成的服务实例都是由一个线程实例化并在其中运行,每个线程都有一个线程的guid来标识这个线程,就是说每个服务实例都可以对应到一个由线程guid标识的线程。运行中的服务实例到主机队列中进行查询,查看是否有可以处理的消息,主机队列要符合一个条件:
l 主机队列中订阅消息的服务类型跟本服务实例类型一致
运行的服务实例找到符合条件的,转入下一步,处理消息。
2.2.2. 实例订阅的实例
实例订阅的时候,同样在线程中运行的服务实例去查询主机队列,同样查看是否有路由到自己这的消息,主机队列要符合两个条件:
l 主机队列中订阅消息的服务类型跟本服务实例类型一致
l 主机队列中服务实例uidInstanceID字段值跟本服务实例的uidInstanceID一致。
如果有符合条件的消息,转入下一步,处理消息。
2.3. 处理消息
轮询的服务实例一旦在主机队列中匹配到待处理的消息,开始处理消息,大致过程如下:
l 把线程的guid写入到实例表的uidProcessID字段,表示这个实例由本线程进行处理。
l 将实例表的nState字段置为2,表示实例在运行中
l 根据主机队列表指示的消息guid,到spool表和parts表中获得消息具体内容进行处理
l 删除主机队列中的本条记录,表示此消息已经开始处理。
l 在服务实例运行过程中,原先在主机队列表中服务实例跟消息实例是有关联的,在删除主机队列中的本条记录后,服务实例跟正在处理的消息就失去了关联,故此,biztalk在删除主机队列记录的同时,把跟本实例的关联的消息guid记入到InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表,这个表主要字段:
uidInstanceID ―― 服务实例的guid
uidInstanceStateID ―― 含义不明
uidWorkID ―― 含义不明
uidMessageID ―― 消息的guid
uidServiceID ―― 服务guid
这样能保证服务实例在运行中能够知道跟本身相关联的消息是哪一个。
2.4. 消息处理完毕后
服务实例处理一个消息,处理完毕后要做的工作:
l 如果是“请求/响应接收端口”服务请求部分订阅产生的激活订阅服务实例完成后,服务实例在实例表中不删除,依然保留,以便这个实例继续处理返回的消息。
l 如果是一般的激活实例或“请求/响应接收端口”服务响应部分订阅产生的实例订阅服务实例完成后,会把这个服务实例的记录从实例表中删除。
l 删除InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表中跟此实例相关记录。