一. 消息发布和路由
消息的发布有几种情况,上面讲述发布服务器时说过的三种发布服务都可以发布消息,他们的发布原理基本一致,这里以适配器发布消息为主进行描述消息发布和路由的过程。
上面的消息流程章节中描述了消息从接收端口接收进入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-1-5-7” |
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 表存放消息的各个部分。