全局流程监视器模型的技术解析

转自http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1002_frank/1002_frank.html

简介

全局流程监视器模型 [5] 以开箱即用的方式监视任意 BPEL 流程,也就是说,不需要生成并部署任何监视器模型或可执行代码。与为流程监视生成模型不同的是(需要跟踪特定流程定义的执行),全局流程监视器模型将检测并跟踪运行在 WebSphere Process Server(此后简称为 Process Server)之上的任何流程并向 WebSphere Business Monitor(此后简称为 Monitor)发送事件。Process Server 必须是 V6.1 或更高版本,而 Monitor 必须是 V6.2.0.1. 或更高版本。

本文将详细地探究这个全局流程监视器模型的设计细节。本文主要有三个意图:

  1. 允许开发人员针对特定的流程定制全局流程监视器模型。虽然您可以从一个生成的监视器模型中构建定制模型,但是从全局流程监视器中开始构建将会生成更健壮的结果,它对流程逻辑更改、数据模型更改、遗失的事件或混乱的事件次序的敏感度更低。它还允许您利用其内置的逻辑来监视调用链、状态历史、变量更改、任务统计数据、流程统计数据、流程迁移,以及其他许多您希望捕捉的观察对象。
  2. 允许开发人员拓宽模型的监视范围,方法就是添加对来自其他流程引擎的事件的订阅。全局流程监视器模型的结构和逻辑并没有绑定到 BPEL。它仅仅是假设流程定义需要执行,而执行由多个步骤组成,并且会有一些事件来报告流程执行及其步骤的状态变化。全局流程监视器捕捉的众多 Process Server 特性(流程变量、流程迁移、任务升级(escalation)等)都是可选的,并且,如果某个引擎没有报告这些内容的其中任何一者,那么它的流程仍然会得到良好的监视。
  3. 提供有关编写监视器模型的高级知识,方法是带领用户遍历模型的设计并讨论一些底层的基本原理。

那些主要希望了解预置指示板中的数据的读者应当参考 [5]。但是,这份参考对于那些希望调整或定制全局流程监视器模型(以获得其内置功能概览)的开发人员来说也是一份不错的介绍性参考资料。本文假设您对监视器模型概念有充分的了解,并且熟悉监视器模型编辑器。您可以在 [1][2] 中找到有关这些主题的介绍。

监视器模型概览

图 1 展示了监视器模型的主要结构。


图 1. 全局流程监视器模型 — 整体结构
全局流程监视器模型 — 整体结构

下面解释为什么会形成这样的结构:我们希望同时在定义级别(M1)和执行级别(M0)监视流程及其步骤和变量。一个流程定义可以具有许多步骤定义。它还可以具有多个执行。一个流程定义可以被分解为多个步骤定义,但是每个步骤执行也可以是一个步骤定义的一个逻辑子分支。因此,要在定义级别和执行级别同时监视流程及其步骤,理想的监视上下文(MC)结构就是如 图 2 所示的菱形。


图 2. 理想的流程监视 MC 结构 — 菱形
理想的流程监视 MC 结构 — 菱形

但是 Monitor 的架构要求监视上下文形成一个树形结构。为了解决这个限制,我们应用了两项不同的技巧来分解这个菱形结构。它们在 图 1 中以红色表示:

  • 要针对步骤执行 解决此问题,每个执行由两个 MC 监视:Process Execution MC(图 1Process Execution Step)的子分支监视给定的流程运行的每一个步骤。Step Definition MC(图 1Step Execution)的子分支监视特定流程步骤的每次运行。通过对每个步骤执行实例化两个 MC,菱形结构得到了分解。等号表示每个 Process Execution Step 监视上下文有一个对应的 Step Execution 监视上下文,但是具有不同的分组和导航属性。
  • 为了针对各种变量 解决这一问题,我们移除了变量定义的 MC 和表示变量定义值的 MC 之间的链接(图 1 中的红色短线)。结果,变量值没有在不同的定义级别聚合,这表示要在指示板上查看它们,您必须通过持有该值的流程执行来进行导航。

这解释了 图 1 所示的模型结构的上半部分,这个部分目前显示为一个树形结构。下半部分显示了一个简单的两层结构,用于监视人工任务定义及其执行。由于 Process Server 允许将人工任务定义为一个流程中的步骤,或者定义为单独的任务,所以只跟踪流程的监视器模型将无法捕捉到全部的人工任务。因此,我们增加了模型的下半部分来解决那些独立的任务。然而,考虑到完整性,Task DefinitionTask Execution MC 同时订阅了来自内联和独立人工任务的事件,这样就可以使用模型的上半部分以 “流程步骤” 的形式监视内联任务,而使用模型的下半部分监视 “人工任务” 形式的内联服务。

我们对监视器模型结构的介绍就此结束。还有许多其他的监视上下文定义,它们很大程度上都具有一些辅助作用:保存应当放在复杂指标类型中的数据。由于 Monitor 只支持简单的指标类型,因此必须使用附加的 MC 定义实现复杂的指标类型以及序列(数组)。几乎所有这些附加的 MC 都捕捉执行状态的历史(带有时间戳)、任务分配、任务升级、模型版本、实例迁移、变量值等等。

图 3 展示了全局流程监视器模型的完整的监视上下文结构,其中强调了主要的监视上下文定义。这些表示主要的导航路径,您在研究此模型时不应当忽略它们。监视上下文名称的一些部分使用括号括起,这表示显示名已被相应地缩写。


图 3. 全局流程监视器模型的监视上下文结构
全局流程监视器模型的监视上下文结构

如果查看监视器模型的源代码,会发现一些名称以 “Aux” 开头的监视上下文定义,这些定义没有显示在 图 3 中;您还会从 图 3 所示的 MC 中发现名称以 “Aux” 开头的指标。所有这些都属于附加结构,用于支持监视器模型逻辑。在设置指示板视图时应当忽略它们,并考虑将它们隐藏起来(特别类似于面向对象编程中的私有字段)。

操作原理

在深入探究细节之前,本节将简单介绍全局流程监视器模型的工作原理,我们将关注核心结构和主要逻辑,故意忽略了许多更细节的东西。这里的描述参考了 图 3

监视器模型的核心设计假设流程引擎将发出以下三种事件:

  • 报告流程执行的状态更改的事件(状态更改包括流程启动、流程暂停、流程恢复、流程终止等等)
  • 报告流程步骤的状态更改的事件(流程步骤包括活动启动、活动终止、活动故障等等)
  • 报告流程变量更新的事件

它还假设有关流程步骤和流程变量的事件将携带一个流程执行标识符,并且所有此类事件都将引用流程定义(有时还会引用服务器,以区别运行在不同位置的同一流程的不同副本)。运行在 Process Server 上的 BPEL 流程将发出所有这三类事件([4])并满足这些标准。当超出这个范围后,我们注意到事件将以类似步骤事件的方式处理。

全局流程监视器模型被构建为对这三种事件类型都不使用强制方式。例如,如果一个流程只发出了变量更改事件,那么是否存在流程执行则是通过这些事件推断得出的;执行的开始和终止时间是未知的,但是将对最早和最迟变量更改事件做出一个估计;对流程步骤本身一无所知。另举一例,对于只发出有关某些人工任务的事件的流程,将按照下面的详细程度执行监视:不会提供有关自动化步骤、执行状态更改、变量值的信息,但是将监视人工任务(从其中接收事件)并推断它们的流程执行。

如何实现上述监视?从主干结构的最底层 MC 开始(图 3 所示的粗体名称):Step ExecutionProcess Execution Step 监视上下文将订阅所有报告运行中的流程的步骤状态更改的事件。所看到的有关步骤执行的第一个事件实例化两个 MC 定义,并且它所报告的状态将被记录。例如,“task Review Expense Account for account #12345 was assigned to user John Doe at 2009-09-09T09:09:09Z”。有关这个步骤执行的所有其他事件将由相同的两个实例接收,并更新步骤执行的历史。

类似地,在接收到有关流程变量(实例)的第一个事件时,Process Execution Variable MC 将被实例化。变量值将被记录。报告变量更新的事件将由同一个监视上下文接收,并将扩展值历史。

在 MC 结构中向上移动,第一个事件将创建一个 Variable Definition 或一个 Step Definition 监视上下文,从其中可以推断是否存在这样一个(变量或步骤)定义。例如,从名为 Review Expense Account 的步骤执行中接收到的第一个事件将创建一个对应的 Step Definition MC。随着更多的 expense accounts 被加入处理,这个步骤将出现更多的执行,并且很可能每个步骤执行拥有多个事件(报告开始、分配、升级、完成,等等)。但是这些只会更新相同的 Step Definition 监视上下文中的统计数据。

Process Execution 监视上下文由第一个事件创建,从其中可以推断是否存在一个新的流程执行。通常,这是一个流程启动事件。但是如果该事件没有被发送,那么报告新执行中的流程步骤或变量更改的第一个事件将执行这项工作。有关该流程执行的后续事件(比如流程暂停、流程恢复、流程终止、流程故障)将在已经存在的监视上下文中更新流程执行的历史,就像那些在更低层 MC 中更新步骤执行历史的事件一样。

在 MC 结构的顶端是一个 Process Definition 监视上下文,它表示在特定服务器上部署的流程定义。从新部署的流程中接收到的第一个事件将创建这个 MC;这个事件可能是有关首次执行的流程启动事件,但是任何其他事件也将这样做。虽然同一个流程被部署到多个服务器上(例如,部署到一个测试服务器和一个生产服务器),两个 MC 结构将被初始化并跟踪运行在两台服务器上的执行。然而,当流程的一个新版本被部署后,顶级监视上下文将保持不变。版本修改将被记录(一个新的 Process Version 监视上下文被创建),但是被认为是流程定义的一次 “演变”,并且执行统计数据将在相同的 Process Definition MC 中持续收集。

Task DefinitionTask Execution MC 的逻辑类似于 Process Definition 和 Process Execution MC 的逻辑。

启用审计事件

Process Server 允许您有选择地启用和禁用审计事件。表 1 提供了一些技巧来确定应该对全局流程监视器启用哪些事件,以及一些基本原理。


表 1. 流程监视的审计事件
要启用的事件原因
对于每一个将要进行监视的流程,在流程级别启用所有事件。 通常,一个流程仅在其生命周期内发出少数一些事件(开始、终止、故障、删除等等),因此这不会引起过度的事件通信量。启用所有事件将使 Monitor 全面地感知执行状态更改,这通常要比启用部分事件所带来的任何性能收益更重要。
对于每一个要监视的活动,同样启用所有事件。 通常,存在关联的活动为人工任务和调用。只启用部分事件,比如活动开始和活动终止,可以视为一种减少事件通信量的方式,但性能收益通常不太显著,而且无法弥补由于信息丢失带来的损失;一个更有效的减少事件通信量的方法是对所有短期运行的步骤、底层子流程、循环体内的步骤等禁用所有事件。
如果同时监视一个流程和它调用的子流程,那么至少要对链接两者的调用活动启用进入事件(entry event)。这允许全局流程监视器捕捉调用者与被调用者之间的关系,如 跟踪调用链 中所述
对希望监视的人工任务(独立的和内联的)启用所有事件。
对循环活动启用所有事件。这将为您提供有关循环迭代的历史,并且带有时间戳。通常这足以监视循环执行,并找出时间消耗在了哪些循环上。它可能还允许您禁用循环体内的任意和所有的事件发出行为。
对短期运行的自动化步骤禁用事件。这明显提升了性能,但是它还帮助避免指示板上出现不必要的混乱,因为这些短期运行的步骤常常与业务用户无关。
对您希望进行监视的流程启用(且仅对它们启用)变量更改事件。另外,专门针对监视分配流程变量将是一个非常有用的技巧:为您希望监视的流程定义变量,并在流程的适当位置映射到这些变量(例如,当实现主要的目标或业务项目经历重要的状态更改)。监视这些变量可以为您同时提供业务级的内容、流程状态和时间线。

入站事件订阅

本文并不打算提供详细的介绍;我们不会遍历全局流程监视器的每个模型元素并进行详尽的介绍。相反,我们将重点关注该模型的几个典型结构并强调其设计的关键方面。一旦理解了这些结构,那么模型的其余部分对于任何查看它的人都会变得易于理解。另一个好处是这些重点介绍的结构可以供您在编写监视器模型时作为参考,让您能够理解可以完成哪些内容以及如何完成。

作为这个监视器模型的入站事件订阅的一个示例,表 2 展示了 Definition、Process Execution 和 Process Execution Step 监视上下文的事件订阅。注意,这五个订阅全部是这三个分层 MC 定义的外部事件订阅。(还有一些针对内部事件的额外订阅,我们将稍后讨论)。


表 2. Process Definition、Process Execution 和 Process Execution Step MC 的事件订阅
事件订阅过滤器相关谓词零个-一个-多个(zero-one-multiple)关联匹配
Process_Definition/Exists fn:exists( Exists/eventPointData/bpc:processTemplateName ) Exists/eventPointData/bpc:processTemplateName = Name and fct:logical-deployment-location( Exists/server ) = Deployed_At create-deliver-error
Process_Definition/Process_Execution/State_Changed fn:exists( State_Changed/eventPointData/bpc:processTemplateName ) and fn:exists( State_Changed/baseData/wbi:eventHeaderData/wbi:ECSCurrentID ) and fn:string-length( State_Changed/eventPointData/bpc:BPCEventCode ) = 5 and fn:contains('21000 21001 21002 21004 21005 21019 21020 21090 42001 42003 42004 42009 42010 42027 42041 42042 42046 42047 42056 42079 42080 ', fn:concat(State_Changed/eventPointData/bpc:BPCEventCode, ' ') ) State_Changed/eventPointData/bpc:processTemplateName = ../Name and fct:logical-deployment-location( State_Changed/server ) = ../Deployed_At and State_Changed/baseData/wbi:eventHeaderData/wbi:ECSCurrentID = Identifier create-deliver-error
Process_Definition/Process_Execution/Step_State_Changed fn:exists( Step_State_Changed/eventPointData/bpc:processTemplateName ) and fn:exists( Step_State_Changed/baseData/wbi:eventHeaderData/wbi:ECSParentID ) and fn:string-length( Step_State_Changed/eventPointData/bpc:BPCEventCode ) = 5 and fn:contains('21006 21007 21011 21021 21022 21027 21080 21081 42005 42015 42020 42021 42022 42024 42026 42031 42032 42036 42037 42038 42039 42040 42050 42054 42055 42057 42061 42062 42064 42065 42066 42067 42068 42070 ', fn:concat( Step_State_Changed/eventPointData/bpc:BPCEventCode, ' ' ) ) Step_State_Changed/eventPointData/bpc:processTemplateName = ../Name and fct:logical-deployment-location( Step_State_Changed/server ) = ../Deployed_At and Step_State_Changed/baseData/wbi:eventHeaderData/wbi:ECSParentID = Identifier create-deliver-error
Process_Definition/Process_Execution/Process_Execution_Step/State_Changed fn:exists( State_Changed/eventPointData/bpc:processTemplateName ) and fn:exists( State_Changed/baseData/wbi:eventHeaderData/wbi:ECSCurrentID ) and fn:exists( State_Changed/baseData/wbi:eventHeaderData/wbi:ECSParentID ) and fn:string-length( State_Changed/eventPointData/bpc:BPCEventCode ) = 5 and fn:contains( '21006 21007 21011 21021 21022 21027 21080 21081 42005 42015 42020 42021 42022 42024 42026 42031 42032 42036 42037 42038 42039 42040 42050 42054 42055 42057 42061 42062 42064 42065 42066 42067 42068 42070 ', fn:concat( State_Changed/eventPointData/bpc:BPCEventCode, ' ' ) ) State_Changed/eventPointData/bpc:processTemplateName = ../../Name and fct:logical-deployment-location( State_Changed/server ) = ../../Deployed_At and State_Changed/baseData/wbi:eventHeaderData/wbi:ECSParentID = ../Identifier and State_Changed/baseData/wbi:eventHeaderData/wbi:ECSCurrentID = Identifier create-deliver-error
Process_Definition/Process_Execution/Process_Execution_Step/Called_Execution_Exists fn:exists( Called_Execution_Exists/baseData/wbi:eventHeaderData/wbi:ECSParentID ) and fn:exists( Called_Execution_Exists/baseData/wbi:eventHeaderData/wbi:ECSCurrentID ) Called_Execution_Exists/baseData/wbi:eventHeaderData/wbi:ECSParentID = Identifier ignore-deliver-deliverToAll

表 3 声明了上述表达式中使用的名称空间前缀。这些声明将在整篇文章中使用。


表 3. 全局流程监视器模型的名称空间声明
前缀名称空间名使用
xs http://www.w3.org/2001/XMLSchema XPath 2.0 类型名称和构造器函数
fn http://www.w3.org/2005/xpath-functions XPath 2.0 内置函数
cbe http://www.ibm.com/AC/commonbaseevent1_0_1 Common Base Event(包装器)事件有效负荷
wbm http://www.ibm.com/xmlns/prod/websphere/monitoring/6.2.0/functions WebSphere Business Monitor 内置函数
wbi http://www.ibm.com/xmlns/prod/websphere/monitoring/6.1 WebSphere Business Integration 事件有效负荷
bpc http://www.ibm.com/xmlns/prod/websphere/scdl/business-process/6.0.0 WebSphere Business Process Choreographer 事件有效负荷
htm http://www.ibm.com/xmlns/prod/websphere/scdl/human-task/6.0.0 WebSphere Human Task Manager 事件有效负荷
evt http://www.ibm.com/xmlns/prod/websphere/monitoring/6.2.0/events/Global_Process_Monitor 全局流程监视器内部事件有效负荷
fct http://www.ibm.com/xmlns/prod/websphere/monitoring/6.2.0/functions/Global_Process_Monitor 全局流程监视器用户定义函数

表 4 展示了 表 2 使用的事件局部定义。


表 4. 全局流程监视器模型中的事件局部定义
标识符路径类型
eventPointData cbe:CommonBaseEvent/wbi:event/wbi:eventPointData 多种类型,例如 bpc:BPC.BFM.PROCESS.BASE
baseData cbe:CommonBaseEvent/wbi:event wbi:Event
server cbe:CommonBaseEvent/cbe:sourceComponentId/@instanceId xs:string

监视上下文的创建和生命周期

返回到 表 2,您会看到顶级 Process Definition 监视上下文只有一个事件订阅,名为 Exists,但是有一个条件非常宽松的过滤器:任何在字段 cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:processTemplateName 中含有一个流程模板名的事件都将通过。由运行在 Process Server 上的 BPEL 流程发出的所有事件都满足这个条件。关联谓词比较了流程模板名和 Name 指标,并比较了其本地部署位置和 Deployed At 指标,因此同一个流程的两次部署(比如分别用于测试和生产)被区分开来:为每个已部署的可执行文件创建一个不同的顶级监视上下文。fct:logical-deployment-location( ... ) 的当前实现提取 WebSphere cell 名(依据当前是否有多个流程副本被部署并监视),然后在不同的 cell 中运行(注意 Process Server 发送的审计事件不会包含 WebSphere cluster 集群信息)。

Exists 订阅的主要用途是在检测到一个新流程时立即实例化一个 Process Definition 顶级监视上下文。如果它这样做了的话,那么其针对 “零个-一个-多个” 关联匹配的设置应当为 create-ignore-error:一旦创建了一个 MC,那么它就不需要继续接收事件。然而,这个订阅接收的事件也会产生下游影响,如 图 4 所示。


图 4. Process_Definition/Exists 事件订阅的下游影响
Process_Definition/Exists 事件订阅的下游影响

图 4 中有三个测量指标,即 Deployed AtNameIdentifier,仅由到达的第一个事件初始化,因此不需要继续向这个监视上下文发送任何事件。但是 Latest Audit EventEarliest Audit Event 时间戳将被持续更新(只更新 Earliest Audit Event,因为这个监视器模型允许事件按任意顺序到达)。此外,Version 指标使用最新部署的流程版本更新(任何此前的版本都被记录到一个 Process Version 子 MC 中),最终,每一个新事件都将从零开始重新启动 Time Since Last Active 计时器(timer)。这个计时器用于在 90 天内没有任何事件到达时终止顶级监视上下文和所有后代上下文(流程随后被认为处于非激活状态)。要修改这个超时值,必须编辑 Process Definition MC 中定义的 Inactive 触发器的触发条件,但是您也许会考虑一个更复杂的实现,该实现可能会调用一个用户定义的函数作为触发条件的一部分,传递 Time Since Last ActiveName 以及 Deployed At 测量指标,并运行一些外部逻辑来确定此时是否应该终止上下文结构。这甚至会涉及到一个人类决策制定者。如果一个流程执行在其 Process Definition 监视上下文被终止后启动,那么一个新的顶级 MC 将被创建并且将会立即恢复监视。然而,统计数据将被区别对待:旧流程的统计数据将被放在已终止的 Process Definition 监视上下文中,而较新的流程的统计数据将被积聚在新 MC 中。

技巧:避免 parent-not-tound 异常

创建 MC 的事件也必须被其父事件(parent)接收到,并且如果其父事件不存在的话,那么将创建其父事件。在一个多层 MC 结构中,必须重复地应用这一原则。

再次参考 表 2,并在监视上下文定义中向下移动,您将看到 Process_Definition/Process_Execution MC 具有两个事件订阅,State_ChangedStep_State_Changed。第一个事件订阅接收所有有关流程执行状态更改的事件(这是在这一级别真正有关的事件)。第二个订阅是 “状态更改” 事件在下一个级别上的订阅副本,即 Process_Definition/Process_Execution/Process_Execution_Step MC 的 State_Changed 订阅:其过滤条件完全一致,并且关联谓词只针对更高的 MC 嵌套层进行了调整。该订阅的惟一意图是在 “步骤状态” 更改事件被发送给 Process Execution Step MC 之前接收它们,并创建所需的父监视上下文(如果不存在的话)。这是一项通用设计原则,将在这个监视器模型的各个部分使用:参考 Avoiding Parent-Not-Found 异常

要实现这点,确保对于每一个可能创建子 MC 的事件订阅,有一个上下文可以在父级别使用相同的或更宽松的过滤器创建事件订阅。查看 表 2 中的示例:

  • Process_Definition/Process_Execution/Process_Execution_Step/State_Changed 接收的事件也会被 Process_Definition/Process_Execution/Step_State_Changed 接收
  • Process_Definition/Process_Execution/Step_State_Changed 接收的事件也会被 Process_Definition/Exists 接收
  • Process_Definition/Process_Execution/State_Changed 接收的事件也会被 Process_Definition/Exists 接收

如果不存在匹配上下文的话,所有这些事件订阅将创建一个新的监视上下文。由于事件总是由顶级监视上下文从上到下发送,这将确保每个新的子上下文都有一个父上下文。注意 Process_Definition/Process_Execution/Process_Execution_Step/Called_Execution_Exists 订阅不会出现问题,因为它永远不会引起 MC 创建。

过滤器表达式

技巧:编写事件订阅过滤器

一个事件订阅过滤器应当 (1) 测试关联谓词中是否具有所需的所有字段,(2) 添加最少数量的必需条件来从所有其他事件中区分所需的事件。

查看 表 2 中的过滤器表达式,您将看到它们都遵循一个模式:首先检查事件中是否存在某些字段,然后检查某些事件代码。编写事件订阅过滤器 中描述了过滤器表达式的一项通用设计原则。

表 2 中的过滤器表达式遵循这条原则,惟一的例外就是没有验证是否存在 /server。这将允许来自未知服务器的事件。在这种情况下,fct:logical-deployment-location( ... ) 函数将返回一个空字符串,以避免出现与关联谓词中缺少字段有关的问题。但是您可以认为这些过滤器被过度定义(over-defined):通过了解由 Process Server 生成的审计事件,您就可以认为如果 cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:BPCEventCode 的计算结果返回值 21000,那么这必须是来自 Process Server 的 BPEL 引擎的 PROCESS_STARTED 事件,包含关联谓词中使用的字段是已知的,因此不需要测试字段是否存在。我们在这里过于谨慎,对本文的演示模型使用了比较复杂的过滤器表达式,但是您可以随意简化它们。会产生一些微小的性能增益,因为测试一个必须取回的字段是否存在基本上不需要花费成本,但是模型的大小将被缩小,并且表达式将变得更具可读性。

测试一组字符串的包含物

表 2 中的过滤器表达式演示的另一个技巧是测试一组字符串中的包含物(containment)。首先要注意,在充分的 XPath 2.0 支持下,BPC 事件代码测试本可以使用一个通用的比较 [3] 编写,如下例所示:

State_Changed/eventPointData/bpc:BPCEventCode = ('21000','21001','21002','21004', ..... )
            

如果左侧的结果(原子化之后)匹配右侧序列中的任意字符串,那么这个表达式为真。但是 Monitor 只提供了有限的 XPath 2.0 支持,您必须按如下所示编码:

fn:string-length( State_Changed/eventPointData/bpc:BPCEventCode ) = 5 
    and
fn:contains(
    '21000 21001 21002 21004 ..... ', 
    fn:concat( State_Changed/eventPointData/bpc:BPCEventCode, ' ' ) )
 

技巧:测试一组字符串的包含物

要测试字符串 S 是否包含在一组字符串中,将这组字符串划分为一些具有相等长度的子集。在每个字符串子集中,使用这些字符串都没有包含的分隔符将字符串连接起来(如果单个字符不满足条件的话,那么使用多个分隔符)。在连接中将分隔符(或多个分隔符)插入到每个字符串的后面,包括最后一个字符串。对于每个已经进行了连接的子集,测试字符串 S 是否具有可以识别的长度,以及 S 与分隔符(或多个分隔符)的连接在整个连接子集中是否包含字符串。对所有子集重复这个过程,然后使用 OR 连接结果。

再次涉及有关 Process Server 审计事件的知识,有人可能认为 BPC 事件代码始终只有 5 个字符长,因此没有必要执行长度检查 —— 但是和前面一样,我们保留了这项检查来演示通用的技巧。注意,在不执行长度检查的情况下,诸如 ‘1000’ 或 ‘004’ 的事件代码将生成错误的匹配,但是即使执行了长度检查,如果事件代码包含空格的话,比如 ‘1001 ’,测试仍然会失败。但是 Process Server 事件代码不会包含空格,在选择分隔符时一定要了解这一点。查看 测试一组字符串的包含物,了解有关此方法的一般描述。

这解释了 表 2 的过滤器表达式是如何形成的:所有 BPC 事件代码的长度均为 5,因此将只对其中一个字符串子集执行连接,并且没有字符串包含空格,因此可以使用空格作为分隔符。最后再举一个例子,假设您希望测试给定的字符串 S 是否是 ‘one’、‘two’、‘too’、‘hot’、‘too much’、‘too cold’ 的其中之一。遵循上面的方法,表达式应为

fn:string-length( S ) = 3 and fn:contains('one,two,too,hot,', fn:concat( S, ',' ) ) or
fn:string-length( S ) = 8 and fn:contains('too much,too cold,', fn:concat( S, ',' ) )
            

由于其中两个字符串包含了空格,因此必须使用不同的分隔符,我们选择使用逗号。注意,如果不执行长度测试,您将为 ‘much’ 和 ‘cold’ 等得到错误的匹配。还要注意,如果选择空格作为分隔符,字符串 ‘much too’ 将生成一个错误的匹配,即使执行了长度测试。

测试可能为空的字段

尽管我们讨论的是用于编写 Boolean 表达式的技巧,但是还有一个技巧通常很有用:当缺少事件字段或指标(为空)时,可以使用两种方式解释:匹配(可选字段,忽略它不会出现问题)或不匹配(强制字段,必须给出且具有预期值)。

结合使用通用比较 [3] 和否定(negation)将允许您对所有可能情况进行编码。下面是一些例子:

event/field = 'foo'           -- field must be present, and value must be 'foo'
event/field != 'foo'          -- field must be present, and value must not be 'foo' 
fn:not(event/field != 'foo')  -- field can be absent, but if present must be 'foo'
fn:not(event/field = 'foo')   -- field can be absent, but if present must not be 'foo'
            

不相同的情况通常也很有用。例如,如果目标是阻塞来自某个源的所有事件,但是要让所有其他事件通过,那么使用表达式 event/source != 'bad_source' 不会生成理想的结果:根本不包含源字段的事件也将被阻塞。正确的表达式应该为 fn:not(event/source = 'bad_source')

最后要记住的是,相同的规则也适用于不等式。下面给出了更多的例子:

event/severity > 10           -- must be present, and must be greater than 10
event/severity <= 10          -- must be present, and must not be greater than 10
fn:not(event/severity <= 10)  -- can be absent, but if present must be greater than 10
fn:not(event/severity > 10)   -- can be absent, but if present must not be greater than 10
            

技巧:测试可能为空的字段

如果缺少一个用于比较的事件字段会生成一个 true 条件,那么使用反向的一般比较(比如 '!=' 而不是 '=' 或 '<=' 而不是 '>'),并结合使用 fn:not( ... )。

因此,要捕捉 severity 为 low 或未指定 severity 的所有事件,那么必须使用最后一个表达式。测试可能为空的字段 中总结了这种方法的基本原理。

然而,请注意,在编写本文时,Monitor V7 中的一个缺陷会导致在某些情况下无法正确地计算包含空参数的一般比较。直到这一缺陷被修复后,才建议您使用 fn:exists()fn:empty() 为空字段包含显式的测试,从而确保获得理想的结果。监视步骤生命周期 中讨论的一些表达式演示了如何使用 fn:exists()fn:empty() 来解决这一问题。在监视器模型调试器中看到的比较结果应当在任何情况下都是正确的。

关联谓词

一个监视器模型定义了一个或多个监视上下文层次结构。(注意:必须通过编码一个 MC 定义 层次结构来定义 MC 实例 层次结构,这是非常不合适的,因此可以被视为编程模型的实现的一个错误。例如,这不利于定义一个所有节点都具有相同类型的监视上下文树,类似于一个反映流程调用堆栈的 MC 树;它还不利于在构建更高级 MC 时重用子 MC 定义)。对于每一个传入的事件,Monitor 必须找到要向其发送事件的一个或多个目标 MC。入站事件订阅的关联谓词在一个监视上下文层次结构中确定这些目标 MC。对于不会引起 MC 创建的事件订阅,通过比较事件中的字段与目标监视上下文中的惟一指标可以实现这个目的。一个例子就是 表 2 中的最后一个关联谓词:

Called_Execution_Exists/baseData/wbi:eventHeaderData/wbi:ECSParentID = Identifier
            

在这里,如果没有发现任何匹配的 MC,那么事件将被忽略(参见 表 2 的最后一列)。然而,对于不会引起 MC 创建的事件订阅,必须有一种方法能够知道应该将新 MC 放到现有 MC 树的哪个位置。为此,这些订阅的关联谓词必须更加详细,并且在新 MC 上的每个级别比较事件字段和一个或多个指标。表 2 中的所有其他关联谓词都属于这种类型。举例而言,下面是一个面向 Process_Definition/Process_Execution/Process_Execution_Step/State_Changed 事件的关联谓词:

State_Changed/eventPointData/bpc:processTemplateName = ../../Name
    and fct:logical-deployment-location( State_Changed/server ) = ../../Deployed_At
                                     -- identify grandparent-level MC (Process_Definition)
                                     
    and State_Changed/baseData/wbi:eventHeaderData/wbi:ECSParentID = ../Identifier
                                     -- identify parent-level MC (Process_Execution)
                                     
    and State_Changed/baseData/wbi:eventHeaderData/wbi:ECSCurrentID = Identifier
                                     -- identify target MC (Process_Execution_Step)
            

当一个传递订阅过滤器的事件到达后,Monitor 首先将查找有多少个现有 MC 匹配关联谓词。可能会出现 0 个、1 个或多个匹配,而 表 2 的最后一列确定了后续的操作过程。对于 Process_Definition/Process_Execution/Process_Execution_Step/State_Changed 订阅,多个匹配将引起错误,一个匹配将造成事件被发送到目标 MC,而 0 个匹配将会促使一个新的 MC 被创建。用于创建 MC 的算法利用了关联谓词的各个部分,如下所示:

  1. 发现了一个具有匹配的 NameDeployed_At 指标的顶级(Process_Definition)MC;此处必须只能有一个匹配,否则将会出现一个错误(或是多个错误,发生在父监视上下文中)。
  2. 发现一个二级(Process_Execution)MC,其中 Identifier 指标匹配事件的 ECSParentID;此处必须只能有一个匹配,否则将会出现一个错误(或是多个错误,发生在父监视上下文中)。
  3. 创建了一个全新的(Process_Execution_Step)MC,并成为一个已识别的更高级 MC 的一个子 MC。

一个新的监视上下文必须始终位于一个 MC 层次结构内(除非它位于顶级),这个要求过于严格,并且按照刚才解释的方式使用关联谓词稍微有点复杂。一个更灵活的架构可以根据 “外键” 支持 MC 引用(模拟关系模型),这个架构将允许 (1) 更多的通用(非树型)结构,(2) 可以在任何时刻建立 MC 关系(不一定要在上下文创建期间)。我们将期待未来 Monitor 版本将带来的新变化

生成的预过滤器

当监视器模型被部署后,将生成一个 Boolean 预过滤器表达式并作为事件选择器用于监视器模型输入队列。理想情况下,预过滤器将让监视器模型所订阅的所有事件都通过过滤,而阻塞其他所有事件。实现此目的的一个简单方法就是对模型中的所有入站事件过滤器形成一个反连接(disjunction)(逻辑 OR)。这种方法的缺点是生成的过滤器表达式会变得非常的大,这会对性能产生负面影响。另一方面,可以生成一个非常简单的预过滤器,对所有入站事件过滤器都具备的条件执行一个连接(conjunction)操作(逻辑 AND)—— 但是这将导致在不存在相同条件时产生一个无用的预过滤器。因此,预过滤器生成器尝试实现一个折中:它将监视器模型的入站事件过滤器划分为至少具有一个相同条件 的子组,并生成一个预过滤器,对每个组的共有条件执行反连接。

下面是一个为全局流程监视器模型生成的预过滤器(针对可读性稍微进行了格式化):

fn:exists(cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:processTemplateName/text())
or fn:exists(cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/htm:taskTemplateName/text())
or fn:exists(cbe:CommonBaseEvent/wbi:event/wbi:eventHeaderData/wbi:ECSCurrentID/text())

or fn:exists(cbe:CommonBaseEvent/evt:stepExecutionEscalation/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:stepExecutionState/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionStepEscalation/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:taskExecutionPotentialOwners/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionState/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:taskExecutionEscalation/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionStepPotentialOwners/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:stepExecutionPotentialOwners/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionVariableUnformattedValue/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:executionId/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:stepExecutionWorkItemUpdate/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:called/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:taskExecutionState/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionStepState/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:processExecutionStepWorkItemUpdate/text()) 
or fn:exists(cbe:CommonBaseEvent/evt:taskExecutionWorkItemUpdate/text())


前三个条件针对所有 Process Server 事件,这些事件同时来自流程和人工任务。事实上,通过在 表 2 的最后一个过滤器表达式(以及针对 ECSCurrentID 测试的其他一些过滤器,但不是流程或任务模板名)中为流程模板名添加一个(冗余)测试,本来可以避免使用第三个条件,但是测试所需的某个事件字段是否存在只需要很低的成本,因此避免执行 ECSCurrentID 测试获得的性能增益是可以被忽略的。

建议用户不要修改生成的预过滤器,因为这大部分都是背景信息,使您能够一览监视器的事件处理算法的 “内部原理”。

日期、时间和持续时间的格式化

图 4 所示,这个监视模型大量使用的一项实践就是所有 xs:dateTimexs:duration 指标都可以作为 XML Schema 格式化字符串获得。例如,指标 Latest Audit Event (xs format)Earliest Audit Event (xs format) 分别包含 Latest Audit EventEarliest Audit Event 的 XML Schema 格式化表示。对指示板显示严格执行了这一操作 —— 并且应当成为一个格式化选项,可以通过在指示板上扭动开关来进行选择。目前,您可以在配置小部件时选择是否显示这两种类型指标的其中一种(或同时显示)。

显示 xs 格式化的字符串将可以展示更多信息,也就是说,可以显示毫秒和时区,并删除了空格来避免使用环绕(wrapaound)效果,这种效果会在表式视图中增加行高。在设置您的指示板时,选择非 xs 版本可以实现更加用户友好的格式,这将显示使用本地时区的时间戳并禁用毫秒,或者使用 xs 格式化的版本,这可以显示更详细的信息,实现在技术上和标准上更兼容的格式,并且可以在表式视图的每个屏幕中显示更多行。全局流程监视器模型附带的基本指示板使用默认格式,而高级指示板则显示 xs 格式化字段。

为了方便演示,图 5 中并列显示了两种格式的时间戳。可以看到,对于默认格式(第一列),没有显示时区信息,这样您必须知道所看到的屏幕截图是在哪里捕捉的,这样才能够了解它所表示的时间点。第一列还显示出任务 1 花费了 1 秒的时间从非激活状态转换到准备(ready)状态,但是第二列显示该时间只用了 0.187 秒。


图 5. 标准时间戳和 xs 格式化时间戳
标准时间戳和 xs 格式化时间戳

参见图 5 的大图。

监视关联集

Process Server 使用关联集将传入的流程事件 —— 这些事件是在流程中单向传递的消息,因此不要与审计事件混淆 —— 与运行中的执行关联起来。虽然可以将关联集看作是与业务人员稍微相关的技术构造,但是它们有时包含一些重要的业务级别的关键字,比如订单号或产品标识符。因此,只要启用了相关的审计事件(事件类型为 42027),全局流程监视器就将捕捉关联集。

然而,Process Server 审计事件使用一种十六进制-二进制格式报告关联集,这种格式必须被转化为一种人类可读的格式。下面是一个示例事件:

  [...]

  
        9.58.26.115;OrderProcessing;;processCustomerOrder;1260472478722;1478383646
      _PI:90030125.7a03ac70.8ce5c5f6.65020000
        9.58.26.115;OrderProcessing;
        sca/dynamic/reference;;processCustomerOrder;1260472478722;1478383646
      6.1CORRELATIONfull42027ProcessCustomerOrder
        Tue 2009-11-10 20:00:00.000
      05
        2 - STATE_RUNNING
      
        _PT:90010124.dfa293ac.8ce5c5f6.ed3f02f9
      admin
        ACED0005757200025B42ACF317F8060854E00200007870000000BD3C3F786D6C20766572
        73696F6E3D22312E30223F3E3C636F7272656C6174696F6E536574206E616D653D226F72
        6465724964656E746966696572223E3C70726F7065727479206E616D653D226F72646572
        44756544617465222076616C75653D22546875204465632032342030303A30303A303020
        4553542032303039222F3E3C70726F7065727479206E616D653D226F726465724E756D62
        6572222076616C75653D223132333435222F3E3C2F636F7272656C6174696F6E5365743E
      

下面是 元素的内容,该内容经过了解码且添加了事件时间戳作为前缀,这就是显示在指示板上的内容:

2009-12-10_14:14:39.753_GMT-05:00

查看 Correlation Sets 指标的映射表达式可能会很有帮助:

if 
  ( State_Changed/eventPointData/bpc:BPCEventCode = '42027' ) 
then 
  fn:substring( 
    fn:concat( 
      fct:format-dateTime-value( 
        xs:string( fn:adjust-dateTime-to-timezone( Aux_Last_Event_Timestamp ) ) ), 
      '
', wbm:escape-special-characters( fct:get-correlation-set-info( State_Changed/correlationSet ) ), '
', Correlation_Sets ), 1, 4000 ) else Correlation_Sets

首先,您看到了一个 if-then-else 表达式,这确保只有 PROCESS_CORRELATION_SET_INITIALIZED 事件(事件代码 42027)可以更新 Correlation Sets 指标。当 State Changed 订阅接收到一个此类事件时,将在 Correlation Sets 指标的前面添加下面的内容:(1) 针对 Monitor server 的本地时区进行了格式化的事件时间戳,(2) 一个换行符,(3) 格式化的关联集信息,(4) 最后是另一个换行符。substring 函数将结果的长度限制为 4000 个字符(字符串指标的最大大小),从而避免在所有关联集的总大小超出该限制时出现运行时异常,这种情况应该会很少见。如果超过一个关联集针对流程执行进行了初始化,那么该指标将显示所有的关联集,在每个关联集的前面添加了它自身的初始化时间戳。对于其他包含使用了时间戳的序列的指标,比如 State HistoryEscalation History 等等,使用子 MC 保存包含时间戳的值。对于关联集,这不是必要的,因为在绝大多数情况下,只有一个或数量很少的关联集。当然,如果您愿意的话,可以添加这些子 MC,使用用于 State HistoryEscalation History 等的模式,这些已在 监视步骤生命周期 中进行了讨论。

下面显示了用于执行十六进制转换的用户定义 XPath 函数,可以用于展示用户定义函数的编码。全局流程监视器模型使用的其他用户定义 XPath 函数将在 用户定义 XPath 函数 中讨论。

public class FormattingUtils {
  public static final String COPYRIGHT="Copyright IBM Corporation 2009."; 
  
  private static final String NAMESPACE_NAME = 
    "http://www.ibm.com/xmlns/prod/websphere/monitoring/6.2.0/functions" 
    + "/Global_Process_Monitor";

  private static final String hexStringRegex = "[0-9A-Fa-f]+";
  private static final Pattern hexStringPattern = Pattern.compile(hexStringRegex);  
  private static final String correlationSetStart = "";
    
  /**
   * Convert a hex string representing a byte array into the equivalent 
   * {@link java.lang.String}, and extract the substring starting with 
   * "". 
   * If the input is not a hex string, or (after conversion) does not contain 
   * such a substring, null is returned.
   * 

* @param input a hex string representing a byte array. *

* @return the substring starting with "" after converting the hex * string passed via input to a {@link java.lang.String}, or * null if the input is not a hex string or no such substring was found. */ @XPathFunction( namespaceName = FormattingUtils.NAMESPACE_NAME, localName = "get-correlation-set-info", description = "Extract a string representation of the correlation set " + "information carried in a PROCESS_CORRELATION_SET_INITIALIZED event issued by " + "WebSphere Process Server (event code 42027).", isSelfContained = true, isDeterministic = true ) public static String getCorrelationSetInfo(final String input) { if (input == null) return null; Matcher hexStringMatcher = FormattingUtils.hexStringPattern.matcher(input); if (!hexStringMatcher.matches()) return null; final byte[] bytes = new byte[input.length() / 2]; for (int i = 0; i < bytes.length; i++) bytes[i] = (byte) Integer.parseInt(input.substring(2*i, 2*i+2), 16); final String result = new String(bytes); final int start = result.indexOf(FormattingUtils.correlationSetStart); final int end = result.lastIndexOf(FormattingUtils.correlationSetEnd); if (start < 0 || end < 0) return null; return result.substring(start, end + FormattingUtils.correlationSetEnd.length()) .replaceAll(">\r\n


使用 XML 格式化的关联集将在审计事件中传递,并且不包含任何格式化的空白。fct:get-correlation-set-info() 函数通过在相邻的元素标记之间插入一个 CRLF 序列来执行一些轻微的格式化。这是通过字符串替换实现的,不需要对 XML 执行适当的解析,并且可以对之进行改进(对于当前实现,一个 CRLF 序列也将被插入到关联集值中的相邻的 ‘>’ 和 ‘wbm:escape-special-characters(),后者使用一个
标记替换 CRLF 序列,这样新的行就会出现在 Web 浏览器中。与 日期、时间和持续时间的格式化 中讨论的 date / time / duration 格式化选项十分类似,这也应当成为一个格式化选项,用户可以在 Instances 小部件中对每一列选择这个选项。目前,该选项由监视器模型逻辑处理。

技巧:在指示板中显示 XML

使用内置函数 wbm:escape-special-characters() 来格式化任何包含 XML 的字符串,从而能够在指示板中进行显示。

注意,XML 特殊字符必须执行转义,这样才能够显示在指示板中,而内置函数 wbm:escape-special-characters() 可以满足这个要求。不需要执行重新格式化,未知的 XML 标记将被大部分 Web 浏览器忽略,因此将变为不可见。

监视执行状态

如前所述,全局流程监视器模型为大量流程和人工任务定义了相对较少的事件订阅。如 表 2 所示,一个 Process Execution Step MC 实际上只有一个事件订阅 State Changed,它订阅了所有报告运行中的流程步骤的状态更改的事件。对于大部分下游处理 —— 触发器、计数器、映射等等 —— 这样做要比对需要进行报告的每种状态更改使用单独的订阅更方便(正如您从 表 2 看到的一样,实现了 34 种不同的事件代码):例如,Time StartedTime EndedName 等指标可以通过一个简单的映射进行更新,因为事件时间戳、步骤名等都位于每种事件的相同位置中。通过对所有状态更改事件使用一个订阅,一个映射就可以替代 34 种事件代码。

一个例外是 State 指标的映射(更准确地说,是指标 Aux Last State ReportedState 指标是从其中派生而来的),这个映射绝不简单。鉴于一些原因,我们将进行简单的介绍,bpc:state 事件字段不仅被复制,并且还显式设置了预定义状态。下面是一个映射表达式:

if ( State_Changed/eventPointData/bpc:BPCEventCode = '21006' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '21021' ) then 'READY' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42020' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '21007' and 
     State_Changed/eventPointData/bpc:state = '3 - STATE_RUNNING' ) then 'RUNNING' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21011' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42026' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42036' ) then 'FINISHED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42005' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42021' ) then 'SKIPPED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21080' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42022' ) then 'FAILED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42023' ) then 'FAILING' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21027' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42024' ) then 'TERMINATED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21022' ) then ( 
     if ( fn:exists( State_Changed/username ) ) then 
     fn:concat( 'CLAIMED by ', State_Changed/username ) else 'CLAIMED' ) else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21007' and 
     State_Changed/eventPointData/bpc:state = '11 - STATE_WAITING' ) then 'WAITING' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '21081' ) then 'EXPIRED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42015' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42066' ) then 'STOPPED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42043' ) then 'COMPENSATING' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42044' ) then 'COMPENSATED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42045' ) then 'COMPFAILED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42031' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42068' ) and 
     State_Changed/eventPointData/bpc:state = '1 - STATE_INACTIVE' ) 
   then 'INACTIVE_FRETRIED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42031' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42068' ) and 
     State_Changed/eventPointData/bpc:state = '2 - STATE_READY' ) 
   then 'READY_FRETRIED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42031' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42068' ) and 
     State_Changed/eventPointData/bpc:state = '3 - STATE_RUNNING' ) 
   then 'RUNNING_FRETRIED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42031' and 
     State_Changed/eventPointData/bpc:state = '11 - STATE_WAITING' ) 
   then 'WAITING_FRETRIED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42031' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42068' ) and 
     State_Changed/eventPointData/bpc:state = '13 - STATE_STOPPED' ) 
   then 'STOPPED_FRETRIED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42032' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42067' ) and 
     State_Changed/eventPointData/bpc:state = '5 - STATE_FINISHED' ) 
   then 'FINISHED_FCOMPLETED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42032' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42067' ) and 
     State_Changed/eventPointData/bpc:state = '4 - STATE_SKIPPED' ) 
   then 'SKIPPED_FCOMPLETED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42032' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42067' ) and 
     State_Changed/eventPointData/bpc:state = '6 - STATE_FAILED' ) 
   then 'FAILED_FCOMPLETED' else (
if ( ( State_Changed/eventPointData/bpc:BPCEventCode = '42032' or 
     State_Changed/eventPointData/bpc:BPCEventCode = '42067' ) and 
     State_Changed/eventPointData/bpc:state = '13 - STATE_STOPPED' ) 
   then 'STOPPED_FCOMPLETED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42037' ) 
   then 'LOOP_CONDITION_TRUE' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42038' ) 
   then 'LOOP_CONDITION_FALSE' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42057' ) 
   then fn:concat( 'FINISHED (', 
     xs:integer( State_Changed/branchesStarted ) + 1, ' branches)' ) else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42061' ) 
   then 'FINISHED_CONDTRUE' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42062' ) 
   then 'FINISHED_ALLCONDFALSE' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42064' ) 
   then 'SKIP_REQUESTED' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42065' ) 
   then 'SKIPPED_ON_REQUEST' else (
if ( State_Changed/eventPointData/bpc:BPCEventCode = '42070' ) 
   then 'SKIPPED_ON_EXIT_CONDITION_TRUE' else (
if ( fn:contains( '42039 42040 42050 42054 42055 ', 
     fn:concat( State_Changed/eventPointData/bpc:BPCEventCode , ' ' ) ) and 
     State_Changed/eventPointData/bpc:state = '1 - STATE_INACTIVE' ) 
   then 'INACTIVE' else Aux_Last_State_Reported
)))))))))))))))))))))))))))))))
            

它实际上是一个非常大的 case 语句,将根据接收到的事件中的一些字段设置步骤执行的状态。这样做是出于以下原因:

  • 显示更加用户友好的语句指定。

    例如,如审计事件所报告的一样,使用 WAITING 代替 11 - STATE_WAITING

  • 显示有关状态的更多细节。

    例如:CLAIMED by John 表示一个任务被分配给 John;或者,FINISHED (5 branches) 表示一个 for-each,后者衍生了 5 个并行线程执行。

  • 显示有关状态历史的更多细节。

    例如,WAITING_FRETRIED 表示活动被强制重试,因此处于等待状态。这要比仅仅报告活动位于 WAITING 状态(可以通过其他方式实现)提供更多的信息。这也比仅仅报告活动被强制重试提供更多信息,因为活动被强制重试可能会产生不同的状态。

  • 允许将相同的状态显示给其他流程引擎。

    它们的审计事件可能不包含 ‘state’ 字段,即使它们包含了该字段,也可能使用了不同的名称。

在这个监视器模型中,这个映射以及面向人工任务执行和流程执行的状态的类似映射是最复杂的表达式。如前所述,对不同的事件使用不同的订阅可以将映射分解为 34 个更小的映射,但是使用当前的方法仍然可以降低整体复杂度:因为大量事件订阅和产生的大量映射不仅会使监视器模型变得混乱,而且,为了做出一些一般更改或获得概览,在一个文本编辑器中查看和编辑一个大的表达式要比单击许多小的表达式更简单。

监视步骤生命周期

在本节中,我们将讨论隐藏在 Process Execution Step 监视上下文的 StateState History 指标背后的逻辑。Step ExecutionTask Execution MC 的 StateState History 指标使用了相同的逻辑,并且类似的逻辑也被用于 Escalation HistoryPotential Owners HistoryWork Item Update History 指标,这些指标出现在相同的监视上下文中。

下面的映射填充了 State Timestamp、State 和 State History 指标,以及它们所依赖的附加指标:

Aux_Last_Event_Timestamp
  = State_Timestamp ) 
          then Aux_Last_Event_Timestamp else State_Timestamp

State                    
  = State_Timestamp ) 
          then Aux_Last_State_Reported else State

When the Aux_Reported_State_Changed trigger fires:

State_History            
  ', 
               State_History 
          ),1, 4000
     )
            

当一个 State Changed 事件到达后,两个附加指标将被直接更新(Aux Last State Reported 指标的完整表达式显示在 监视执行状态 中)。当事件时间戳没有早于 State Timestamp,或者一直没有设置 State Timestamp 的话,附加指标将被分别复制到 State TimestampState 指标中。

使用这种逻辑的理由是处理无序到达的 State Change 事件。因为 Monitor 使用内置的逻辑来确保来自 Process Server 的事件具有合适的次序,因此没有必要监视运行在 Process Server 上的 BPEL 流程。然而,可能会添加没有提供事件序列标识符的事件源,并且为了演示通用的一般技巧,因此模型中保留了这种逻辑。

细心的读者可能会注意到,通过使用 测试可能为空的字段 中的建议,State TimestampState 的映射表达式可以被简化:

State_Timestamp          
  

但是,为了解决由于出现空参数而引起的一般比较的当前问题,面向空 State Timestamp 指标的测试被变为显式的。

State History 指标的更新由 Aux Reported State Changed 触发器控制,该触发器将针对每一个接收到的 State Changed 事件进行评估,并且其条件为:

Aux_Last_State_Reported != Aux_Saved_Last_State_Reported or
  fn:starts-with( Aux_Last_State_Reported, 'LOOP_CONDITION' )
            

引入触发器是为了防止在连续的 State Changed 事件报告相同的状态时对状态历史进行扩展。对 LOOP_CONDITION_TRUE / LOOP_CONDITION_FALSE 生成一个异常,因为在一个 do-while 或 while-do 循环的开始/结束部分记录重复的 true / false 计算是有意义的。当触发器被触发后,State History 指标的前面会被加上 XML Schema 格式化时间戳、所报告的最后一个状态,以及一个换行标记。fn:substring() 函数将结果的长度限制为 4000 个字符,以避免超出字符串指标的最大长度后出现异常。这种情况应当很少见,但是包含大量迭代的循环可能会引起这种条件。

Task Execution MC 的 State History 指标的屏幕快照如 图 6 所示,它显示了与 图 5 相同的状态历史。


图 6. 在 Task Execution 监视上下文中显示 State History 指标
在 Task Execution 监视上下文中显示 State                     History 指标

参见图 6 的大图。

可以清楚地看到这个指标如何引起过高的行高,这也是为什么这些时间戳历史被移到子监视上下文的原因之一。(另一个原因是能够捕捉长期运行的循环的状态历史)。您可以看到在 State History 指标的前面显示了指向子 MC 的下钻(drill-down)链接;向下钻取将引导您至 图 5 所示的视图。State History 和其他此类指标(Escalation HistoryPotential Owners HistoryWork Item Update History)都没有显示在全局流程监视器附带的指示板配置中,但是它们确实存在于模型中。可以在指示板配置中随意使用它们,而不是使用下钻链接,提供一个包含所有内容的单一页面来显示流程捕捉的执行状态。此外,State History 指标用于计算人工任务处于等待和工作状态的持续时间,我们将在 收集执行统计数据 中加以讨论。

使用下面的方式创建用于保存包含时间戳的状态记录的子 MC:当事件的 State 指标发生变化时,从 Step Execution MC 中发送出事件,然后由同一个监视器模型接收事件。(Task Execution MC)出站事件定义和相应的(Task Execution State History 子 MC)入站事件定义分别如 图 7图 8 所示。


图 7. 用于任务状态报告的出站事件定义
用于任务状态报告的出站事件定义

参见图 7 的大图。

图 7 展示了由 4 个指标组成的这些事件的有效负荷:

  • 任务定义标识符,这是顶级 MC 键
  • 任务执行标识符,这是二级 MC 键
  • 最后一个事件的时间戳,这将成为第三级 MC 键
  • 任务执行状态,这是该事件承载的真正的有效负荷数据

子 MC 中的事件订阅过滤器(参见 图 8)将检查有效负荷中是否存在某个任务执行状态字段。它定义了相同的事件部分作为其对等的出站事件定义,名为 emptyString 的事件部分除外,我们将稍后讨论。


图 8. 任务状态报告的入站事件定义
任务状态报告的入站事件定义

参见图 8 的大图。

查看 图 8 中的关联谓词,您将看到前两个比较将对祖父 MC 键和父 MC 键与事件有效负荷中的对应字段进行比较,这是 MC 创建订阅的要求。最后一个比较的值始终为 false,但是满足关联谓词的正式条件,即对目标 MC(Time)中的指标与依赖于传入事件的表达式执行比较。emptyString 字段永远不会存在于一个传入的事件,并且将切换到一个 dummy xs:dateTime 值来防止关联谓词中与空字段有关的任何问题。当然可以使用其他的方式来构建比较,使其满足这些常规的要求,但始终得到 false 值:图 8 中的表达式就是这样一个例子。图 5 中看到的 TimeState 指标是根据事件中的相应字段设置的,而 Time (xs format) 指标仅仅是通过对 Time 进行重新格式化得到的,使用了如下表达式:

fct:format-dateTime-value( xs:string( fn:adjust-dateTime-to-timezone( Time ) ) )
            

标准函数 fn:adjust-dateTime-to-timezone() 在使用一个单个 xs:dateTime 参数调用时,将其调整为在其上运行的服务器的本地时区。用户定义函数 fct:format-dateTime-value() 随后将调整过时区的时间戳格式化为 yyyy-mm-dd_hh:mm:ss.sss_GMT[+|-]hh:mm 。您可以在 图 5图 6 中查看结果。

监视流程版本

正如从 表 2 中看到的一样,在关联表达式中没有使用流程定义版本(模板版本)来标识 Process Definition MC,因此,如果在一段时间内在同一个服务器上部署了多个不同的流程版本,来自这些版本的事件将被接收到同一个 Process Definition MC 下的 MC 子树下,并且组成了相同的统计数据集。Process Definition MC 中的 VersionVersion No Blanks 指标始终包含由最后一个事件报告的版本(即当前运行的版本)。然而,流程定义的所有版本都由 Process Version 子监视上下文记录,这个上下文的指示板呈现类似于 图 9


图 9. 在指示板上显示流程定义版本
在指示板上显示流程定义版本

查看图 9 的大图。

图 9 中的流程定义名是多余的(可以在父 MC 中查找),但是为了方便使用,对它进行了复制。版本可以显示空格,也可以不显示空格,但是大多数其他 MC 实例视图使用了不包含空格的格式,以避免在字符串被环绕为多个行时出现行高增高的现象。最后,将显示来自流程版本的最早的和最迟的审计事件的时间戳,用于确定处于活动状态的时间跨度。

流程模板版本由 Process Version MC 的事件订阅记录,如 图 10 所示。


图 10. 检测流程定义版本
检测流程定义版本

查看图 10 的大图。

Detected 事件订阅接收来自 BPEL 流程的任何事件,这个 BPEL 流程承载了一个流程定义名(processTemplateName)和一个版本标识符(processTemplateValidFrom)。关联谓词在父上下文中引用流程定义名和服务器(如果给出的话),并比较事件中的版本标识符与 Process Version 子 MC 中的版本标识符。如果没有发现匹配,即,如果这是第一个报告新流程版本的事件,那么将创建一个新 MC。否则,事件将被发送给这个版本的现有 MC,并用于更新 Earliest Audit EventLatest Audit Event 时间戳。

监视流程迁移

从版本 7 开始,Process Server 允许运行中的流程执行在不同流程定义版本之间迁移。此类迁移涉及一些事件,这些事件将由全局流程监视器接收并用于报告这些迁移。 有两种 MC 定义与流程实例迁移相关:Process MigrationProcess Execution Migration。当在两个流程版本之间检测到任何迁移时将对第一个 MC 定义实例化。它将记录 “一个或多个运行中的执行被从流程定义版本 A 中迁移到流程定义版本 B 中”。第二个 MC 定义将针对流程执行的每次单独迁移进行实例化。它将记录 “运行中的执行 #123 已从流程定义版本 A 迁移到版本 B”。

图 11图 12图 13 展示了一些样例屏幕快照。每幅图像都显示了一个包含两行的宽指示板屏幕。图 11 展示了两个 Process Migration MC,它们记录一个或多个执行在 CheckItemAvailabilityAndPrice 的以下版本之间进行了迁移:

Tue_2009-12-15_00:00:01.000 -> Tue_2009-12-15_00:00:02.000
Tue_2009-12-15_00:00:02.000 -> Wed_2009-12-16_00:00:00.000
            

您还可以看到最早和最迟迁移的开始和结束时间。


图 11. 发生迁移的流程定义版本
发生迁移的流程定义版本

查看图 11 的大图。

图 12 展示了已迁移的流程执行的 5 个 Process Execution MC 的屏幕快照。在第二列显示的版本 Wed_2009-12-16_00:00:00.000 是迁移的目标版本。可以看到这些执行在 Process Execution Migration 列中被迁移。单击下钻链接以查看每一个迁移的详细信息。


图 12. 迁移后的流程执行
迁移后的流程执行

查看图 12 的大图。

图 13 展示了通过 图 12 的链接下钻到 Process Execution Migration MC 的结果。您将看到流程定义的名称、旧的和新的版本、旧的和新的流程定义标识符,以及发生迁移所持续的时间间隔。通过上钻到 Process Execution 级并再次下钻到 Process Execution Steps,您可以看到执行在此刻所处的位置。


图 13. 一条流程执行迁移记录
一条流程执行迁移记录

查看图 13 的大图。

总的来说,全局流程监视器将按照以下方式跟踪流程执行迁移:

  • 流程定义的新版本始终被记录到 Process Version MC 中,附属于 Process Definition MC。不管执行是否迁移到新的版本,都会发生这个操作。
  • 如果一个或多个执行在流程定义的两个版本之间迁移,那么将记录到 Process Definition MC 的 Process Migration 子 MC 中。子 MC 将显示迁移发生在哪两个版本之间,以及发生迁移的时间跨度。
  • 一个流程执行发生的每次迁移都被单独记录到 Process Execution Migration MC 中,后者从属于被迁移的执行的 MC。注意,被迁移的执行将继续由同一个 Process Execution 监视上下文跟踪。迁移记录将显示在哪两个流程定义版本之间发生迁移,以及发生迁移的时间跨度。
  • 当被迁移的执行开始发出事件时,被添加、忽略的步骤以及新流程版本所带来的任何其他逻辑更改都将被自动记录。这是因为全局流程监视器将在运行时动态检测流程结构。

Process MigrationProcess Execution Migration MC 的逻辑非常简单:接收报告迁移开始或结束的事件,并用之创建或更新它们的目标 MC。需要注意的是,Process MigrationProcess Execution Migration 监视上下文的关联键包含一对儿流程定义标识符,表示流程定义的 “源” 和 “目标” 版本。

监视流程事件

BPEL 流程可以定义事件处理器,这个事件处理器可以与整个流程关联,也可以与某个范围关联,并且由传入的流程事件激活。流程事件是在流程执行中单向传递的消息,不会与流程执行发出的审计事件混淆。“流程事件” 一词用于那些入站单向消息。全局流程监视器将使用 Process Execution Event MC 跟踪流程事件,这个 MC 表示由与其父 MC 对应的流程执行接收的流程事件。

图 14 显示了审计事件的事件订阅,审计事件将报告在运行执行时到达的流程事件。对于每个接收到的流程事件,只要求具有一个审计事件,从 zero|one|multiple 匹配的关联设置 create|ignore|error 中可以看到这一点。报告流程事件的 MC 使用事件时间戳作为键,前提条件是一个流程执行不会在完全相同的时间报告接收到两个流程事件。


图 14. 报告流程事件的审计事件的订阅
报告流程事件的审计事件的订阅

查看图 14 的大图。

对于接收自流程级别的事件处理器的审计事件和接收自范围级别的事件处理器的审计事件,有多种不同的订阅。这是因为由这两种事件处理器发出的审计事件截然不同。事实上,它们非常不同,以至于会定义两种类型的 MC,一种用于流程级别的事件处理器,一种用于范围级别的事件处理器。表 5 总结了两者之间的差异。


表 5. 由流程级别和范围级别的事件处理器发出的审计事件的内容
内容在来自流程级别事件处理器的审计事件中的位置(PROCESS_EVENT_RECEIVED / 事件代码 42047)在来自范围级别事件处理器的审计事件中的位置(SCOPE_EVENT_RECEIVED / 事件代码 42048)
消息(业务有效负荷)cbe:CommonBaseEvent/wbi:event/wbi:applicationData/wbi:content n/a
WSDL 操作cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:operation cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:operation
WSDL 端口类型名cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:portTypeName n/a
WSDL 端口类型名称空间cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:portTypeNamespace n/a
接收到的事件处理器被附加到的范围n/a cbe:CommonBaseEvent/wbi:event/wbi:eventPointData/bpc:activityTemplateName

至于为什么不同类型的事件处理器使用不同的审计事件结构报告流程事件的接收,原因不太明显。通过将流程级别的审计事件报告的一些数据添加到范围级别的审计事件,应该可以实现两者的聚合。

跟踪调用链

对这个特性的需求出现在早期回顾全局流程监视器及其指示板时。有些人问到用户如何才能知道大部分时间花费在了哪里。因此目标变成显示一个流程执行的调用堆栈,以及在每个子流程级别花费的时间。一旦识别出那些时间密集型的流程执行后,那么应该可以很轻松地了解它们的步骤,从而定位比较重要的任务。图 15 展示了为了实现此目的而添加的指示板视图。


图 15. 可视化子流程调用
可视化子流程调用

查看图 15 的大图。

这个指示板视图仅仅是对 Process Execution MC 的 Instances 视图做了不同的布局。在这里,没有根据流程定义对执行进行过滤,相反,根据初始的(顶级)流程执行进行分组,而在每一个组的内部根据起始时间进行排序。您可以在 图 15 中看到两个顶级执行,标识符分别为 PI:90030125.9d7f4954.90e5c5f6.3c30ce6PI:90030125.9fc856e3.90e5c5f6.7cbc0031。通过将调用深度设为 0 来标记它们,并且流程定义名没有使用缩进。第一个是 ProcessCustomerOrder 的执行,第二个是递归流程 DoItAgain 的执行。紧接着顶级执行之后的是其第一级子流程的执行,其调用深度为 1。可以看到 ProcessCustomerOrder 执行对 CheckItemAvailabilityAndPrice 调用了 5 次,随后又调用了一次 ProcessCustomerInvoice。还会看到 DoItAgain 对其自身执行了 4 次递归调用。Indented Process Definition Name 列保存流程(其执行显示在该行中)的名称,但是将根据调用深度进行缩进。DoItAgain 的例子展示了相同的流程定义可以具有不同级别的执行;因此,作为顶级、一级、二级等子流程并不属于流程定义的一部分。

图 15 的 total duration、waiting duration 和 working duration 列中,可以看到 ProcessCustomerOrder 执行的大部分时间花在了顶级流程上,而不是其任意子流程。您将继续研究这个执行来找出一些长期运行的任务。持续时间也揭示出花在子流程上的大部分时间是因为要等待一名用户去声明一个人工任务;一旦声明此任务后,通常只需要几秒钟的时间就可以完成。图 15 还显示了 DoItAgain 流程并没有调用任何人工任务,因为等待和工作的持续时间在每一级都为 0。

显然,了解每个流程执行的发起者(initiator)标识符和调用深度是启用该视图的关键。但是如何确定这些内容?由流程执行发送的审计事件并没有包含这些信息。事实上,一个运行 10 个调用级别深度的流程执行并不了解这方面的任何内容:它不知道有 10 个更高级别的执行正在等待它返回,也不知道有关堆栈顶部的流程执行的任何内容。在这里就可以使用 Monitor 的全局优点来添加各个执行都不具备的信息。

让我们看一看这个过程是如何实现的。一般来说:调用子流程的流程执行的监视上下文将以 XML 格式收集它们的所有执行标识符,这些内容保存在一个字符串指标中。子流程执行标识符来自由这些子流程发出的任何流程级别的事件。当一个新的子流程执行在其调用者的监视上下文中变为已知时,该监视上下文将向子流程执行的监视上下文发送一个内部事件,通知它有关自身的发起者标识符和调用深度。子流程执行 MC 向调用深度加 1,并采用相同的发起者标识符。一旦子流程启动并提供了它们的执行 ID,那么就可以开始对子流程执行相同的操作。总而言之,通过使用内部事件(从发出调用的执行的 MC 流向被调用的执行的 MC),调用深度和发起者标识符将在堆栈中向下传递。


图 16. 监视器模型逻辑支持可视化子流程调用
监视器模型逻辑支持可视化子流程调用

有关底层逻辑的更详细描述,请参见 图 16,其中显示了位于顶部的发出调用的流程执行的监视上下文,位于中间的调用步骤的上下文,以及位于底部的被调用的流程执行的上下文。显示的惟一模型元素是在逻辑流中起到重要作用的元素,使用如 图 16 所示的步骤编号描述:

  1. 第一个审计事件(报告流程执行是否存在)创建发出调用的流程执行的 MC,并设置其 Identifier
  2. 使用流程执行的标识符初始化 Initiator Identifier(复制自 Identifier)。
  3. Invocation Depth 被设置为 0。
  4. 流程执行到达一个调用行为,针对该行为创建一个 Process Execution Step 子上下文。其 Identifier 通过调用行为的执行标识符初始化。此时对于这个被调用的执行还一无所知,它将要在流程服务器上启动。只有在它已经启动并且开始发出审计事件时才能够捕捉其执行标识符和其他数据。
  5. 被调用的流程执行的 MC 被创建,且它的 Identifier 被设置,和调用方的 MC 一样,这些操作由第一个审计事件(报告子流程执行是否存在)完成。
  6. 至于调用方的 MC,Initiator Identifier 使用子流程执行自己的标识符执行初始化(复制自 Identifier)。
  7. 对于调用方 MC,Invocation Depth 被设置为 0(目前是这样)。
  8. 由子流程执行发出的流程级别的事件将在其 ECSParentID 字段中包含调用行为的执行标识符。这样,就可以通过 Called Execution Exists 订阅在调用行为的监视上下文中接收它们。
  9. 接收自子流程的事件在 ECSCurrentID 字段中包含它的执行标识符;它被存储在 Called Execution Identifier 指标中。
  10. 将计算 Aux Add Called Execution Identifier 触发器。它的触发条件将测试子流程执行 ID 是否还没有包含到已知子流程执行标识符列表中,这个列表位于父上下文中的 Aux Called Execution Identifiers 指标中。当子流程执行 ID 被第一次报告时,那么将不会找到它,因此触发器将被触发。
  11. 触发器获得 Aux Called Execution Identifiers 指标在父上下文中的一个映射,这将新的子流程执行标识符附加到已知的被调用的执行标识符列表中。我们现在实现了上述一段话中的第一句话所描述的内容:“调用子流程的流程执行的监视上下文将以 XML 格式收集它们的所有执行标识符,这些内容保存在一个字符串指标中”。子流程执行 ID 被作为一串 XML 元素存储在 Aux Called Execution Identifiers 指标中,其形式为 called-execution-identifier
  12. Aux Add Called Execution Identifier 触发器还获得了 Aux Called Executions 指标的映射,其中包含 wbm:send-events() 函数。wbm:send-events() 函数将仅发送一个事件,包含三个有效负荷元素:被调用的执行标识符;调用方的发起者标识符;以及调用方的调用深度。(包含这些 wbm:send-events() 函数的 Aux Called Executions 映射如下所示)。
  13. 通过 Aux Report Initiator Identifier And Invocation Depth 订阅,内部事件在被调用的执行的监视上下文中接收。被调用的执行标识符被用作关联键。
  14. 被调用的流程执行的 Initiator Identifier 在传入的内部事件中被重写。
  15. 被调用的流程执行的 Invocation Depth 也被重写,方法就是向传入的内部事件所报告的深度加 1。
  16. 完成了前面这些步骤后,被调用的流程执行的监视上下文具有正确的 Initiator IdentifierInvocation Depth,并且可以将它们传递给其自己的子流程执行的 MC。但是前提条件是在完成上面的 15 个步骤,这些子子流程(sub-sub-process)变为可知,而这是无法保证的。内部事件(报告正确的发起者标识符和调用深度)可能会阻塞在来自已经启动的子子流程的事件之后。这些事件将在表示更低级的执行的 MC 之间触发前面描述的相同过程,但是将在被重写之前传递子流程执行的默认设置(Initiator Identifier = own identifierInvocation Depth = 0)。为了实现这种可能性,传入的内部事件(通过另一个触发器,Aux Initiator Identifier And Invocation Depth Changed,该触发器未显示在 图 16 中)触发了另一个 wbm:send-events() 函数,该函数将更新后的 Initiator Identifier and Invocation Depth 值传递到任何以及所有已知的子子流程执行 MC,方法就是遍历 Aux Called Execution Identifiers 中的执行 id 列表。

以下是在 图 16 中的第 12 步和第 16 步中调用的 wbm:send-events() 函数。注意,通过第一个参数传递的 XML 片段是使用字符串连接动态构建的。

When trigger Aux_Add_Called_Execution_Identifier fires:

wbm:send-events( 
    fn:concat( 
        '',
        Process_Execution_Step/Called_Execution_Identifier, 
        '' ), 
    '/evt:called', 
    'fn:concat( 
        "", 
        $currentItem/text(), 
        "", 
        $var2, 
        "", 
        $var3, 
        "" )', 
    " xmlns:evt='... URI ...'", 
    Initiator_Identifier, 
    xs:string( Invocation_Depth ) 
)


When trigger Aux_Initiator_Identifier_And_Invocation_Depth_Changed fires:

wbm:send-events( 
    fn:concat( 
        '', 
        Aux_Called_Execution_Identifiers, 
        '' ), 
    '/evt:root/evt:called', 
    'fn:concat(
        "", 
        $currentItem/text(), 
        "", 
        $var2, 
        "", 
        $var3, 
        "" )', 
    " xmlns:evt='... URI ...'", 
    Initiator_Identifier, 
    xs:string( Invocation_Depth ) 
)


In both expressions, "... URI ..." stands for the namespace URI

http://www.ibm.com/xmlns/prod/websphere/monitoring/6.2.0/events/Global_Process_Monitor
            

第一个调用的 XPath 表达式(/evt:called)始终生成长度为 1 的结果,并且将发送一个出站事件。第二个调用的 XPath 表达式(/evt:root/evt:called)将生成 元素的序列,目前存储在 Aux Called Execution Identifiers 指标中。它可以保护 0 个或多个项,并且将发送出站事件的相应编号:每一个此刻已知的子流程执行都有一个编号。

技巧:通过多个列对 Instances 视图排序

要在 Instances 小部件中通过多个列对视图排序,定义一个附属字符串指标作为将用于排序的指标串。Pad 数值指标包含前导零(leading zero),所有其他指标包含结尾空格(trailing blank),最大长度为每个所涉及的指标的固定长度。将填充的结果连接起来以设置辅助指标。不要在 Instances 视图中显示辅助指标,而是将它用于排序。

在结束本节时,需要注意一下 Process Execution MC 指标的另外一个辅助指标,它用来支持 图 15 所示的调用链视图:Aux Initiator Identifier And Time Started (xs format)。这是必需的,因为 Instances 小部件不允许根据多个列对行进行排序,但是需要在发起者标识符内根据起始时间进行排序,以生成 图 15 中的视图。要解决这个限制,通过以下方式设置辅助指标:填充将用于排序的指标,直到达到一个固定的长度,然后将结果连接起来;辅助指标随后被用于排序。这个方法可以被一般化以应用于任意数量的列中(参见 根据多个列对 Instances 视图排序)。

收集执行统计数据

全局流程监视器模型的一个重要目标就是收集执行统计数据,比如每一个被监视的流程定义的最大、最小或平均持续时间。在监视器模型中实现这种合计的第一种选择就是使用 KPI。然而,虽然指标存在于监视上下文(可以在部署和启动新流程时动态添加)中,但是 KPI 为预定义的单独内容。不能够在 Process Definition 监视上下文中像 “平均执行时间” 那样定义 KPI。从监视器模型架构的角度来看,这是不合适的,并且理想情况下,通过允许使用合计函数设置指标,并通过 KPI 小部件显示指标,指标和 KPI 之间的差别将消失。但是就目前来讲,可以具备多个实例的合计值必须使用指标定义。

如果流程执行从来没有重启过,那么合计它们的持续时间将会变得很简单:每当一个执行结束时,它的持续时间将被添加到一个总和,已完成的执行的数量将加一,被重新计算为这些数值的商的平均值,以及最大和最小值将根据需要进行调整。但是当一个流程执行被重新启动时,所有这些合计值都必须被重新调整:被添加到总和的流程持续时间必须被再次减去,已完成的执行的数量将减少,而最小或最大值可能需要被重设会之前的值。在全局流程监视器中用于计算合计值的大量逻辑可以用来准备或处理此类 “撤消” 场景。

让我们详细讨论 Process Definition MC 中的一些合计指标的逻辑。所示的例子代表了此监视器模型中的所有合计,并且具有类似逻辑的指标可以在 Task DefinitionStep Definition MC 中找到。图 17 展示了为总的流程持续时间计算的 5 个合计(最小、最大、总和、平均、标准偏差),以及实现这些计算所涉及的模型元素。


图 17. 支持收集流程执行统计数据的监视器模型逻辑
支持收集流程执行统计数据的监视器模型逻辑

下面概况了如何合计流程执行的总持续时间,以及在重启执行时如何重新调整。步骤编号对应于 图 17 中的编号。

  1. Boolean 指标 Has Ended 被初始化为 false。
  2. Boolean 指标 Aux Has Ended At Least Once 被初始化为 false。
  3. 指标 Aux Saved Total Duration 没有被初始化(保留为空)。
  4. 指标 Total Duration 在来自被监视的流程执行的事件到达时被持续更新。下面是它的一个映射表达式:
    Total_Duration 

    该映射依赖于 Time StartedTime Ended(未显示在 图 17 中)。指标 Time Started 保存了任何接收自被监视的流程执行的事件的最早时间戳。指标 Time Ended 保存了报告流程执行的最终状态的事件的时间戳(将 Has Ended 切换到 true)。在设置了 Time Ended 后,它将被用于计算 Total Duration,但在此之前,将使用当前事件的时间戳。因此,当流程处于运行状态时,Total Duration 指标表示的是 “流程处于激活状态的持续时间”。下面是一些相关的映射表达式。
    Time_Started 

  5. Has Ended 标志切换为 ture 时,将开始计算合计。下面是其映射表达式:
    Has_Ended 

    显然,当流程执行被重启后,其状态切换到一个不同的值(比如 ‘RUNNING’),而 Has Ended 再次切换为 false。
  6. 指标 Aux Has Ended At Least Once 变为 true,但与 Has Ended 不同,它将不会再变为 false。下面是它的映射表达式:
    Aux_Has_Ended_At_Least_Once 

  7. 每当 Has Ended 从 false 切换为 ture,都将触发 Aux Has Ended 触发器。当 Has Ended 被更新时将计算触发器,并且其条件就是该指标;该触发器是不可重复的,因此需要在再次触发前将 Has Ended 变为 false。
  8. 触发器 Aux Has Ended 将增加 Process Definition MC 中的 Finished 计数。这是结束的流程执行在父监视上下文中的第一个影响。
  9. 触发器 Aux Update Aggregate Durations 将在 Has EndedTotal DurationWaiting DurationWorking Duration 发生变化时进行计算。其触发条件为:
    Has_Ended and 
        fn:exists(Total_Duration) and 
        fn:exists(Waiting_Duration) and 
        fn:exists(Working_Duration) and 
        fn:not( Total_Duration = Aux_Saved_Total_Duration and 
                Waiting_Duration = Aux_Saved_Waiting_Duration and 
                Working_Duration = Aux_Saved_Working_Duration )
                    

    因此,它将在一个流程执行结束时触发,它的总持续时间、等待持续时间和工作持续时间都是可用的,并且不同于它们的已保存的副本(已经用于合计计算的值)。与 Aux Has Ended 触发器(在每次流程执行结束时只触发一次,包括在重启之后)不同,Aux Update Aggregate Durations 触发器还在以下情况下触发:在执行的状态被作为最终状态报告后,一些最新的事件更新了总持续时间、等待持续时间和工作持续时间。这是针对无序到达事件将监视器模型变得更健壮的众多方法之一。
  10. Aux Update Aggregate Durations 触发后,图 17 中的四个 Process Definition 指标更立即更新。下面是它们的映射表达式:
    Aux_Candidate_Max_Total_Durations 

    从列表底部开始,您看到通过添加流程执行的新的总持续时间,并减去已经被保存的任何旧值,Sum Total Duration 被更新。Aux Sum Squares Total Duration 使用相同的方式计算,例外之处是添加了总持续时间的平方值,并减去了旧值的平方值(如果有的话)。更新 Aux Candidate Min Total DurationsAux Candidate Max Total Durations 的用户定义函数实际上执行相同的 “remove-old-add-new” 操作,但是使用的是一个持续时间值列表:对于通过第一个参数传递的持续时间列表(以逗号分隔),fct:update-duration-list() 函数添加通过第四个参数传递的持续时间,而删除通过第 5 个参数传递的持续时间。这将返回修改后的列表。参见 用户定义 XPath 函数 获得详细内容。

    紧接着以上 4 个指标后,父 MC 中的另外 4 个指标也将被更新,下面是它们的映射表达式。这些映射不是由触发器控制的,因此将在所依赖的任何指标被更新时运行。所有映射的含义都应当很清楚。用户定义函数 fct:get-max-duration()fct:get-min-duration() 以逗号分隔列表的形式返回最大/最小持续时间值,该列表被作为参数传递。注意,这里用于计算最大和最小持续时间的方法(使用了备选的最大/最小值的辅助列表)对于在一个参与值在流程重启期间被撤回时对最大/最小值进行调整是至关重要的:当多个参与的流程执行被重启时,回滚到先前的值将会失败(因为一些早前的值可能已经变为无效)。
    Avg Total Duration     0 ) 
         then Sum_Total_Duration div Finished 
         else Aux_Empty_Duration
    
    Max Total Duration     0 and
              fn:exists( Aux_Sum_Squares_Total_Duration ) and
              fn:exists( Avg_Total_Duration ) ) 
         then 
              xs:dayTimeDuration( 
                   fn:concat( 
                        "PT", 
                        fct:sqrt( Aux_Sum_Squares_Total_Duration div Finished - 
                             ( Avg_Total_Duration div xs:dayTimeDuration('PT1S') ) * 
                             ( Avg_Total_Duration div xs:dayTimeDuration('PT1S') ) 
                        ), 
                        "S" 
                   ) 
              ) 
         else 
              Aux_Empty_Duration
                    

    这将结束 Process Definition MC 中的所有合计的总持续时间的更新。针对可能的流程执行重启,还需要另外一个步骤:保存用于更新统计数据的总的持续时间值。接下来两个步骤将实现这一点。
  11. 当完成由 Aux Update Aggregate Durations 触发器驱动的所有更新后,触发器 Aux Save Durations 将触发。
  12. 指标 Total Duration 被复制到 Aux Saved Total Duration
  13. 触发器 Aux RestartedHas Ended 从 true 切换到 false 时触发。下面是它的触发器条件: fn:not( Has_Ended ) and Aux_Has_Ended_At_Least_Once 每当 Has Ended 发生变化时,将对该条件进行评估,当 Has Ended 被设置为 false 时(在 MC 被创建时),与 Aux Has Ended At Least Once 的关联是必需的,以防止该触发器在最开始的时候就被触发。
  14. Finished 计数器将由 Aux Restarted 触发器减一,现在再一次反映了已完成的执行的正确数量(减少一个)。
  15. 在父 MC 中执行了一系列更新,这将撤消步骤 10 的所有更新:保存的总持续时间被从备选的最小/最大总持续时间列表中移除(15a,15b)。保存的总持续时间及其平方值将分别从指标 Sum Total DurationAux Sum Squares Total Duration 中减去(15c,15d)。当所依赖的 4 个指标和计数器被设置回原先的值后,Avg Total DurationMax Total DurationMin Total DurationStdDev Total Duration 指标将被重新计算(15e —— 15h),这将完成 Process Definition MC 中的撤消操作。
  16. 当触发器 Aux Restarted 促使所有统计数据值都进行了重新调整后,触发器 Aux Clear Saved Durations 将触发。
  17. 指标 Aux Saved Total Duration 被设置为空(在流程结束前的值)。

可以看到重新启动逻辑(通过 Has Ended 再次变为 false 后发起,这将触发 Aux Restarted)重新建立了状态,之后流程执行出现一个异常:指标 Aux Has Ended At Least Once 没有被重新设置为 false;当然,这是故意这样做的。

技巧:在祖先监视上下文中计算指标的合计值

使用本文讨论的技巧,可以在父(或更高级)监视上下文中通过渐增的方式计算指标的合计值。如果值在计算完总和后不会改变,那么计算逻辑将非常简单。当已经计算了总和的值发生改变,或者被删除并从合计中移除,那么需要更复杂的逻辑。全局流程监视器模型演示了所需的全部技巧。

本节展示的技巧用于计算流程执行的总持续时间的计数、最小值、最大值、总和、平均值和标准方差,这些技巧可以被广泛地使用,以获得一个父(或更高级)监视上下文中的子指标值的合计(参见 在祖先监视上下文中计算指标的合计值)。

以上用于计算流程执行的总持续时间的总和、平均值、最小值、最大值和标准方差的算法还被用于在流程定义级别计算等待持续时间和工作持续时间的合计值。此外,这些技巧还用于计算流程步骤和人工任务在其定义级别的总持续时间、等待持续时间和工作持续时间的合计值 —— 重试操作现在起到重新启动流程执行的作用,并触发必要的调整。

为了进行全面的观察,图 18 展示了 Aux Update Aggregate Durations 触发器在 Process Execution MC 中的所有下游影响(图 17 中的 #9)。用于更新总持续时间的模型元素(见 图 17 和上文的讨论)使用红色标记。


图 18. Aux Update Aggregate Durations 触发器的下游影响
Aux Update Aggregate Durations 触发器的下游影响

查看图 18 的大图。

计算子 MC

计算监视上下文内的子 MC 的数量通常都很有用。例如,流程执行的步骤的数量等于其 Process Execution MC 的 Process Execution Step 子 MC 的数量,而其变量的数量等于其 Process Execution Variable 子 MC 的数量。理想情况下,要获得这些计数,应当能够在 Process Execution MC 中定义一个指标,且映射类似于

Variables

Monitor 中的当前 XPath 2.0 支持并不允许这样做;然而,您可以在一个父 MC 中定义一个计数器,它将在每次创建一个子 MC 时加一。在决定如何控制这个计数器时,必须区分两种情况:如果引起子 MC 创建的订阅最多接收一个事件,那么它们可以用于在父 MC 中直接对计数器加一。但是如果这些订阅在一个子 MC 创建后继续接收事件,那么必须使用一个初始化触发器:该触发器没有条件,不可重复,并且链接到所有创建子上下文的事件订阅。注意链接可以是间接的,方法就是让触发器依赖于每个创建事件都将更新的指标(例如 MC 键)。

图 19 展示了一个初始化触发器的例子。当 Accessed 订阅接收到有关流程执行变量的第一个事件时,一个新的 Process Execution Variable 上下文将被创建,触发器 Aux Detected 将被计算,由于它没有包含条件,因此将触发。由于该触发器是不可重复的,并且它的条件始终为 true,因此不管随后接收到多少 Accessed 事件,都不会再触发它。触发器将在 Process Execution 父监视上下文(图 19 中未显示)中对 Variables 计数器加一。


图 19. 一个监视器上下文初始化触发器
一个监视器上下文初始化触发器

查看图 19 的大图。

技巧:计算子 MCs

为了计算监视上下文中的子 MC 的数量,在该上下文中定义一个指标,如果创建子 MC 的事件订阅最多接收一个事件,那么所有这些事件订阅都将对此计数器加一,否则,由一个子 MC 初始化触发器对计数器加一。初始化触发器不包含条件,它是不可重复的,并且链接自任何创建子 MC 的事件订阅。因此,它在一个子 MC 被创建时只会触发一次。 要计算当前的活动子 MC,计数器必须减去子 MC 终止触发器。

通过刚才描述的方法,就可以计算已经被初始化的子 MC 的数量。要计算当前活动的子 MC 的数量,子 MC 的任何终止触发器都必须对计数器减一。计算子 MC 总结了这些过程,并且可以使用相同的方法来在多个后代级别上计算 MC。

计算任务持续时间

对于人工任务,全局流程监视器将记录三个持续时间:

  • 总持续时间:从任务开始到任务达到最终状态所经过的时间
  • 等待持续时间:任务处于 READY 状态所经历的时间
  • 工作持续时间:任务处于 CLAIMED 状态所经历的时间

考虑 图 20 所示的状态历史。


图 20. 人工任务状态历史
人工任务状态历史

查看 20 的大图。

这个任务执行由用户 admin 声明,随后取消任务分配,然后再由该用户声明,最后结束。因此,该任务在两个时间间隔内等待用户声明它,这两个阶段都构成了等待持续时间。在这两个时间间隔内,任务还处于被声明状态(工作状态),这又构成了工作持续时间。研究 图 20 所示的视图,我们来看一下对这个任务执行的总结,如 图 21 所示。


图 21. 人工任务执行记录
人工任务执行记录

查看 21 的大图。

这里也显示了 State History 指标,它复制了 State History 子 MC 中的信息,并且通常被隐藏起来。您很快会看到,它在计算任务的等待持续时间和工作持续时间方面扮演着重要的作用。进行了计算的读者会很快发现 Waiting Duration 等于处于 the READY 状态的时间,而 Working Duration 等于处于 the CLAIMED 状态的时间,Total Duration 则表示任务从开始(最初处于 INACTIVE 状态)到结束所经历的时间。下面是这三个持续时间指标的映射表达式:

Total_Duration   

您会看到一个任务执行的 Total Duration 的映射表达式与一个流程执行的 Total Duration 映射表达式相同,我们已经在 收集执行统计数据 中讨论了后者。等待持续时间和工作持续时间的映射将 State History 指标(包含任务的完整的已记录状态历史)传递给用户定义函数 fct:aggregate-duration-in-state()。该函数计算作为第一个参数传递的状态所花费的时间。当前事件的时间戳通过第三个参数传递并用作时间间隔的终点,这个时间间隔使用最近的状态历史记录作为起点。例如,如果最近的状态记录为 CLAIMED,那么从对应的状态更改一直到通过第三个参数传递的时间戳之间的时间将被添加到 Working Duration 中。

fct:aggregate-duration-in-state() 函数的一个重要方面就是它将在计算任何时间间隔之前按照时间戳对状态历史记录进行排序。这是用于处理无序到达事件的另一项措施:如果报告任务状态变化的事件没有按顺序到达,那么 State History 指标中的记录也是无序的。但是这不会影响对任意状态下所用时间的计算,这些计算将在对状态转换记录排序后进行。(注意,类似 图 21 所示的下钻视图中的记录将始终按时间排序,因为它们都由 Instances 小部件排序)。

累计任务持续时间

前面的小节解释了如何根据状态变化事件计算人工任务的等待持续时间和工作持续时间。但是全局流程监视器指示板还显示了调用和流程执行的等待持续时间和工作持续时间。它们的定义如下:

  • 流程执行的等待/工作持续时间是指其人工任务和调用的等待/工作持续时间的总和。
  • 调用的等待/工作持续时间与被调用的流程执行的等待/工作持续时间相同。

因此,流程执行的等待/工作持续时间就是指这些执行以及它们直接或间接调用的任何子流程中的所有人工任务的累计等待/工作持续时间。实现整个堆栈的持续时间的总和需要另一种内部事件。让我们看看实现这一目标的详细逻辑。


图 22. 累计调用堆栈中的任务持续时间
累计调用堆栈中的任务持续时间

为了解释用于累加人工任务持续时间的逻辑,图 22 展示了两个 Process Execution MC,每个 MC 拥有一个 Process Execution Step 子 MC。位于上部的 MC 表示发出调用的流程执行(父流程),其子 MC 表示调用步骤。位于下方的 MC 表示被调用的流程执行(子流程),其子 MC 表示这个流程内的一些人工任务。

下面简单描述发生的操作:将从人工任务的状态历史计算它的等待/工作持续时间,如 计算状态持续时间 所述。这些持续时间将在流程执行的级别上累加,同时还包括这个流程执行中的所有人工任务和调用的等待/工作持续时间。每当这些合计值发生变化时,一个内部事件将从表示流程执行的 MC 发送到表示其调用的 MC 中(图 22 中的箭头),报告新的累计持续时间。调用的等待/工作持续时间将从事件中设置。将在(调用流程的)流程级别上累加,同时包含该流程执行内的所有人工任务和调用的等待/工作持续时间。

下面更详细地描述了这些步骤,其中使用了 图 22 的数字编号:

  1. 发出调用的流程执行到达调用步骤。创建了 Process Execution MC 的一个子 MC 来表示调用并执行了初始化。具体来讲,指标 Aux Saved Working Duration 被初始化为一个长度为 0 的持续时间(PT0S)。
  2. 开始执行被调用的流程执行,并且将创建一个相应的 MC。其 Identifier 被设置为新执行的流程实例 ID。
  3. 跟踪调用链 所述,被调用的执行的实例标识符也在表示调用的 MC 中捕捉,包含在指标 Called Execution Identifier 中。
  4. 被调用的流程执行开始执行一些人工任务。创建了其 Process Execution MC 的子 MC 来表示这个人工任务执行,并进行了初始化。具体来讲,指标 Aux Saved Working Duration 被初始化为长度为 0 的持续时间(PT0S)。
  5. 开始处理人工任务时,其状态历史将被更新。
  6. 将从人工任务的状态历史计算其 Working Duration,如 计算任务持续时间 中所述。
  7. 每当 Working Duration 的设置值不同于 Aux Saved Working Duration 中最近保存的值时,触发器 Aux Update Aggregate Working Duration 将被触发。
  8. 每当人工任务的 Working Duration 发生改变,并因此触发了它的 Aux Update Aggregate Working Duration 触发器,那么将在流程级别调整 Working Duration。下面是映射表达式:
    Working_Duration 

  9. 流程级别的工作持续时间在更新后被报告给出站事件,出站事件承载了新的工作持续时间以及流程执行标识符。
  10. 这个事件的发出完成了 Aux Update Aggregate Working Duration 触发器的最直接的下游影响。接下来,链接的触发器 Aux Save Working Duration 将在人工任务 MC 中触发。
  11. 结果是,任务的新 Working Duration 被复制到 Aux Saved Working Duration,以为流程执行级别的另一个更新做准备。
  12. 内部事件由表示调用步骤的 MC 接收。关联谓词利用了 MC 具有被调用的执行的标识符(存储在指标 Called Execution Identifier 中)这一事实。
  13. 事件所报告的工作持续时间被保存在指标 Aux Called Execution Working Duration 中。
  14. 调用步骤的工作持续时间被设置为所报告的持续时间。其映射表达式为:
    Working_Duration  

    注意,步骤 6 使用了相同的步骤来根据人工任务的状态历史设置它的工作持续时间,并且这个映射实际上是一个开关(switch):如果 Aux Called Execution Working Duration 中有一个被报告的持续时间(只针对调用步骤),那么将使用它设置该指标。如果不是的话,那么将从 State History 指标计算它的值,即处于 CLAIMED 状态所用的时间;结果将为 0,除非该步骤是被分配给用户的人工任务。
  15. 和步骤 7 相同,触发器 Aux Update Aggregate Working Duration 将触发。
  16. 正如在步骤 8 中一样,调用步骤的更新后的 Working Duration 被添加到(调用)流程执行的 Working Duration 的合计中。

使用完全相同的逻辑计算等待持续时间的累计值。

注意,这里使用一个内部事件来在堆栈内向上填充信息,从表示被调用的流程执行的 MC 开始,一直到表示其调用方的 MC。将其与 跟踪调用链 中描述且在 图 16 中显示的逻辑相比较,其中,内部事件被用于在堆栈中向下传递信息。

最后,请注意具有并行特性的流程,流程执行的工作或等待持续时间会很容易超出它的总的(流逝的)持续时间,同样的事情也会发生在流程定义级别:比如,流程定义的平均等待持续时间会超出它的平均总持续时间。

处理混乱的事件顺序

技巧:处理无序事件

如果入站事件订阅的下游映射包含可能来自早期事件的数据,那么需要对映射执行测试,检查所有必需的数据是否到达,然后通过其中任意的组成事件触发它们。如果无法这样做的话,那么在关联谓词中为任何需要的数据包含测试,并在没有发现关联匹配时指定 “重试” 选项。

通常有两种技巧可用于处理那些无序到达的事件,这两种技巧在 处理无序事件 中做了总结,下面对它们进行了解释。

这里给出有关第一个技巧的例子。作为一种计算任务持续时间的简单方法,您可能会在一个监视器模型中编写以下逻辑(Start_TimeDuration 为指标;Task_StartedTask_Ended 为入站事件订阅):

Start_Time  

意图很明显。首先,报告任务启动的事件将设置 Start_Time 指标;随后,报告任务完成的事件设置 Duration 指标,方法是从其自己的创建时间中减去启动时间。但是如果启动事件是在完成事件之后到达,那么这种方法就会失效。

作为一种替代方法,考虑下面的逻辑:

Start_Time  

现在,启动事件和完成事件可以按任意顺序到达。每个事件都会将其创建时间保存在一个指标中。在获得所有时间戳中后,将计算出任务的持续事件。

这个技巧在全局流程监视器的很多位置得到了使用,并且很已经在前面见过了它的例子,比如在 收集执行统计数据 的步骤 4 的 Total Duration 映射表达式中。一个更复杂但风格相同的技巧是用于计算人工任务的等待和持续时间的方法,这在 计算任务持续时间 中做了解释:人工任务的状态变化将被记录,同时记录的还有它们的时间戳,记录的顺序就是它们被报告的顺序。但是在计算持续时间之前,将对被记录的状态历史进行排序,这显然会使模型在处理无序到达的状态更改事件方面更健壮。还有一个例子,如 收集执行统计数据 中的步骤 9 所述,就是在 Process Execution MC 中设置触发器 Aux Update Aggregate Durations:当流程执行已经被报告为完成后,一些最新的事件更新了执行的总持续时间、等待持续时间或工作持续时间,此时将继续触发触发器。

第二个技巧的例子就是 Process Execution Step MC 中的 Aux Receive Called Execution Waiting DurationAux Receive Called Execution Working Duration 事件订阅。如果针对它们所订阅的事件的关联失败了,那么这意味着来自子流程调用步骤的事件还没有到达(而来自子流程执行的事件已经到达),或者是表示子流程调用步骤的 MC 还没有接收到一个包含被调用的执行的标识符的事件。随着越来越多的事件到达,应当恰当地处理这两种情况,因此,选择了一个重试选项来处理这种情况下出现的混乱事件顺序。

技巧:使用计时表(计时器)

不要使用计时器来计算两个事件之间的时间间隔(在发生第一个事件时启动计时器,在发生第二个事件时停止计时器)。相反,如此出所述,使用事件的时间戳来计算时间间隔。计时器可用于测量 “自某个时刻起的时间”(与 “时间间隔” 相反),特别是当最终事件可能会无限延迟或根本不会发生时。

注意,计时表(定时器)对于那些无序到达的事件尤其敏感:如果启动时间在完成事件之后到达,显然结果是没有意义的。因此,与体育赛事中的计时表不同,监视器模型计时器不应该用于计算两个事件之间的时间(参见 使用计时表(计时器))。

在超过一个预定义的持续时间后仍没有接收到一个心跳信号或其他预期响应时需要发出警告,这时计时器就派上用场了。当等待时间开始后可以启动计时器,并且一个循环触发器将不断比较计时器与阈值持续时间。当计时器超出定义的阈值后,触发器将触发,并且将发出一个警告。其他使用计时器的例子包括任何需要在指示板上显示运行中的时钟的场景。

如果使用第一种方法处理无序到达事件(即所有事件被接收,保存了有效负荷数据,并且下游操作被阻塞,直到所有所需的信息变为可用),那么会产生一个问题,即任何这些事件都必须创建目标 MC(如果不存在的话)。如果每个事件都包含了目标 MC 或任何祖先 MC 的必需的键,那么就可以使用这个策略。全局流程监视器模型就是按这种方式编写的。如果不能实现每个事件都创建 MC 的话,那么应当考虑重试选项:如果事件到达后尚不存在任何目标 MC,那么事件将被延迟,直到创建 MC 的事件到达。

注意:

  1. 在撰写本文之际,Monitor V7 中存在一个已知的问题:在部署监视器模型的新版本时仍然存在活跃的 MC,并且这些旧的 MC 的创建事件可能会在部署新模型版本后达到。在这种情况下,这些创建事件将根据新的监视器模型定义实例化 MC,同时它们也应当能够被使用旧模型逻辑的现有 MC 使用。全局流程监视器模型允许几乎所有传入的事件创建所需的监视上下文,因此可能会遇到这个问题。为了避免此问题,有两种方法,一是永远不要部署新的监视器模型版本,或者,如果必须部署的话,在切换到新版本之前将旧的模型版本设置为静默模式(确保没有活动的 MC)。
  2. 有时候,由于事件中没有包含目标 MC 的父 MC 的键(目标 MC 键通常可以在事件中找到,因为需要使用它实现关联),因此不可能为每个事件生成一个潜在的 MC 创建事件。MC 创建事件必须包含目标 MC 及其祖先 MC 的键,这一需求是可以理解的,但是仍然是不合适的。对关联谓词产生的复杂性已经在 入站事件订阅 的结束部分中指出。这里提到的妨碍因素(在需要时难以获得更多用于创建 MC 的事件)是另一个缺点。理想情况下,监视器模型架构不应当将 MC-MC 关系强制实现为一种分级关系,并且不应要求在子 MC 被创建时建立这些关系。相反,MC-MC 关系应当根据外键指标而动态变化,并且可以在任意时间建立,比如在参与的 MC 被创建或使用所需的值更新时。

性能考虑

现在您应当很清楚,未作修改的全局流程监视器模型可能并不适合那些性能非常关键的环境。一个传入事件可能会引起许多个次要事件,这是一个严重影响性能的因素,但是某些情况下,可以很轻松地处理它。在不影响模型的任何关键功能的情况下,许多内部事件都可以被禁用。

下面是一些性能调优技巧:

  • 对于 Aux Report Waiting DurationAux Report Working Duration 异常(在 累计任务持续时间 中已作讨论),这个监视器模型的出站事件定义只用于将历史记录移动到子 MC,以实现更好的可视化(参见 监视步骤生命周期)。删除它们以及由这些内部事件实例化的子 MC 定义,将不会影响全局流程监视器的任何功能。所有历史记录都可以在 State HistoryEscalation History 等字符串指标中捕捉到,并且在指示板上显示这些指标(而不是子 MC)的惟一缺点就是会使行高增高,但是不会丢失任何信息。
  • 通过删除 Aux Called Executions 指标的定义移除 wbm:send-events() 函数(在堆栈中向下传递发起者标识符和调用深度),除了丢失发起者标识符和调用深度值外,不会产生任何负面影响。这些值仅用于指示板可视化(它们支持 图 15 所示的 Invocation Chains 视图),并且忽略这些值不会对模型逻辑的其他部分产生影响。
  • Process Execution MC 定义中删除出站事件定义 Aux Report Waiting DurationAux Report Working Duration 将导致调用步骤永远不会具有等待或工作持续时间。因此,流程执行的等待和工作持续时间将只反映它自己的人工任务,而忽略了在子流程中执行的任何任务。这是模型逻辑以及这些持续时间的语义的一项重大变化。如果可以接受这个改变的话,那么就可以删除这些出站事件定义来获得更好的性能。
  • 前面的这些步骤目前为止应该已经实现了最重要的性能改进,因为它们避免了所有次要的(内部)事件。如果需要获得更进一步的性能增益,那么下一个逻辑步骤就是删除不必要的指标。可供考虑的指标包括标准方差和用于计算的 Aux Sum Squares 指标,以及经过 xs 格式化的日期/时间和持续时间指标。
  • 如果所有这些操作仍然不能够为模型提供足够好的性能,那么可能需要编写一个定制的模型。

最后需要记住的是,通常可以通过截断事件源来获得大量的性能:对任何以及所有不需要进行监控的流程步骤、流程变量甚至完整子流程禁用事件发出。

用户定义 XPath 函数

表 6 对全局流程监视器使用的用户定义 XPath 函数进行了总结。


表 6. 全局流程监视器使用的用户定义 XPath 函数
函数签名描述
fct:add-to-list(xs:string?, xs:string?) xs:string? 将通过第二个参数传递的一个或多个字符串(对于多个字符串必须使用 XML 标记包围)附加到由第一个参数传递的字符串值列表(使用逗号分隔)中。每个分隔的逗号之后必须有一个空格。第二个参数中的 XML 标记只用于分隔它们所包含的字符串;如果只传递一个字符串,那么用于包围的 XML 标记是可选的(如果给出的话,那么在添加字符串值之前将移除它)。字符串只有在列表当前没有包含它们时被附加。
fct:aggregate-duration-in-state(xs:string?, xs:string?, xs:dateTime?) xs:duration? 针对一个状态转换历史(通过第二个参数传递),计算通过第一个参数传递的状态的持续时间的合计值。状态转换必须被编码为以空格作为分隔符的转换时间戳序列,后跟目标状态。这个序列不需要按照时间排序。最终状态可能包含空格,但是空格不会在第一个或最后一个字符位置。第三个参数表示当前时间,用于计算最近进入的状态的持续时间。它被默认为系统时钟的当前时间。
fct:convert-indenting-blanks(xs:string?) xs:string? 使用   符号替换任何用于表示缩进的空格。
fct:format-dateTime-value(xs:string?) xs:string? 格式化 xs;dateTime 值的词汇表示,实现更好的可读性。
fct:get-correlation-set-info(xs:string?) xs:string? 从由 Process Server 发出的 PROCESS_CORRELATION_SET_INITIALIZED 事件(事件代码 42027)中提取关联集信息的字符串表示。
fct:get-max-duration(xs:string?) xs:duration? 从被传递的 xs:duration 值的逗号分隔列表中返回一个具有最大长度的持续时间。
fct:get-min-duration(xs:string?) xs:duration? 从被传递的 xs:duration 值的逗号分隔列表中返回一个具有最小长度的持续时间。
fct:logical-deployment-location(xs:string?) xs:string? 返回可能的复合标识符的第一部分,在复合标识符中,要求使用斜杠或反斜杠分隔各个部分。
fct:prefix-N-times(xs:string?, xs:string?, xs:integer?) xs:string? 使用第二个参数传递的字符串作为第一个参数传递的字符串的前缀,第三个参数表示次数。如果第三个参数不是正数的话,那么将原封不动地返回第一个参数。如果第一个参数为空的话,那么结果也将为空。
fct:remove-content-wrapper(xs:string?) xs:string? 从参数值删除 封装器,但是保留所有需要的名称空间声明。
fct:remove-from-list(xs:string?, xs:string?) xs:string? 从通过第一个参数传递的字符串值的逗号分隔列表中,删除通过第二个参数传递的一个或多个字符串(如果是多个字符串的话,必须放在连续的 XML 标记中)。每一个用于分隔字符串的逗号之后必须有一个空格。第二个参数中的 XML 标记只用于分隔它们所包含的字符串;如果只传递一个字符串,那么用于包围的 XML 标记就不是必需的。通过第二个参数传递的字符串如果没有在第一个参数中找到的话,那么将被忽略。如果通过第二个参数传递的字符串在第一个参数中出现了多次,那么将被全部移除(删除每一个出现的字符串)。
fct:replace-all(xs:string?, xs:string?, xs:string?) xs:string 如果第一个参数中的任何子字符串匹配通过第二个参数传递的正则表达式,那么使用通过第三个参数传递的字符串替换这些子字符串。如果第二个参数为空,那么将原封不动地返回第一个参数。如果最后一个参数为空,那么将用空字符串替代任何匹配。
fct:sqrt(xs:decimal?) xs:decimal? 计算一个十进制数字的平方根。
fct:update-duration-list(xs:string?, xs:duration?, xs:boolean?, xs:duration?, xs:duration?, xs:integer?) xs:string? 按照如下所示更新一个序列化的 xs:duration 值列表(通过第一个参数传递):作为第二个参数传递的持续时间被认为是一个下限值或上限值,这取决于作为第三个参数传递的 Boolean 值(‘true’ 表示下限,‘false’ 表示上限)。任何超过这个限制的持续时间值将被从列表中移除。作为第四个参数传递的持续时间值被添加到列表中,只要这个值没有超出指定的范围,即使这个列表已经包含有该值(允许相同的值)。作为第五个参数传递的持续时间被从列表中删除(如果包含它的话)。如果第二个、第四个或第五个参数为空,那么将不会应用任何截止(cut-off)范围,并且不会在列表中添加或删除任何持续时间。最后一个参数限制了结果的长度:如果指定了该参数的话,xs:duration 值将从结果列表中移除,直到它不再超出这个长度;当第三个参数为 ture(false)时,最短(最长)持续时间将被删除。

有关更详细的描述,请参见 简化的 Javadoc 文件,或者 用户定义函数的源代码

维度模型

图 3 所示,为监视上下文的结构定义了一个完整的维度模型。它允许您设置大量维度视图,按照各种方式对收集自全局流程监视器的数据进行切分。图 23 展示了两个例子。


图 23. 一些维度视图
一些维度视图

查看 23 的大图。

图 23 上方的图表显示了针对三种不同流程定义的已经启动的流程执行的数量(橙色),正在处理中的流程执行的数量(绿色),以及已经完成的流程执行的数量(蓝色)。这个视图使用了一个基于 Process Definition 监视上下文的多维数据集(cube),一个基于流程定义名的维度,以及三个指标 StartedIn ProgressFinished,这三个指标反映了 Process Definition MC 中的同名计数器的总和。由于每个维度值只有一个 Process Definition MC(除非有多个重复的流程名),因此不会计算总和,并且只会得到这些计数的图形化呈现。

图 23 下方的图表显示了流程执行的总持续时间、等待持续时间和工作持续时间的最大值、平均值和最小值。流程定义(显示了其执行时间的总和)使用下拉菜单选择(选择了 ProcessCustomerOrder)。这个视图使用了一个基于 Process Execution MC 的多维数据集,一个基于流程名的维度以及 9 个指标。这些指标表示具有相同流程名的所有流程执行的总持续时间、等待持续时间和工作持续时间的合计值。注意,这些相同的合计值被保存在 Process Definition MC 的指标中,并按照 收集执行统计数据 中的描述递增。您可以使用由多维数据集引擎计算的指标来验证通过监视器模型逻辑计算的 Process Definition 指标;它们必须匹配。

为了使您对预定义的维度模型有一个了解,表 7 展示了 7 个多维数据集中的其中两个。这些多维数据集也为 图 23 的图表提供了数据。


表 7. 维度模型中的两个多维数据集
CUBE: Process Definition Cube
MC: Process Definition
MeasureSource Metric/Counter/Timer/KeyAggregation Function
Started Started (C)Sum
Finished Finished (C)Sum
In Progress In Progress (M)Sum
Min Duration Inactive Time Since Last Active (T)Minimum
Max Duration Inactive Time Since Last Active (T)Maximum
Dimension / LevelsSource Metric/Counter/Timer/Key
Process Definition Id / Identifier Identifier (M)
Process Definition Name / Name Name (M)
CUBE: Process Execution Cube
MC: Process Execution
MeasureSource Metric/Counter/Timer/KeyAggregation Function
Min Total Duration Total Duration (M)Minimum
Avg Total Duration Total Duration (M)Average
Max Total Duration Total Duration (M)Maximum
StdDev Total Duration Total Duration (M)Standard deviation
Sum Total Duration Total Duration (M)Sum
Min Waiting Duration Waiting Duration (M)Minimum
Avg Waiting Duration Waiting Duration (M)Average
Max Waiting Duration Waiting Duration (M)Maximum
StdDev Waiting Duration Waiting Duration (M)Standard deviation
Sum Waiting Duration Waiting Duration (M)Sum
Min Working Duration Working Duration (M)Minimum
Avg Working Duration Working Duration (M)Average
Max Working Duration Working Duration (M)Maximum
StdDev Working Duration Working Duration (M)Standard deviation
Sum Working Duration Working Duration (M)Sum
Executions Identifier (K)Count
Dimension / LevelsSource Metric/Counter/Timer/Key
Process Definition Id And Execution State / Identifier / State Process Definition Identifier (M) / State (M)
Process Execution State / State State (M)
Process Name / Name Process Definition Name (M)
Indented Process Name / Indented Name Indented Process Definition Name (M)
Initiator Identifier / Identifier Initiator Identifier (M)

监视器要求这些用于维度的指标具有默认值,这解释了为什么没有维度用于执行的开始和结束。如果需要这样的维度,可以添加指标 Time Started With DefaultTime Ended With Default 来实现此目的,并赋予它们默认值(对 Time Started With Default 使用较早的时间点,对 Time Ended With Default 使用较迟的时间点)。一旦设置它们后,您将通过复制映射从实际的 Time Started 和 Time Ended 指标中重写它们。

预定义 KPI

虽然维度模型非常全面,但是 KPI 模型并不是这样的。如 收集执行统计数据 中所述,主要原因在于 KPI 被 Monitor 定义为一些独立的合计数字,没有在监视上下文中被实例化,而有用的合计值几乎总是 “流程定义” 或 “任务定义” 级别的。了解任何流程的平均执行时间,或者是任何任务的最小和最大工作时间,通常不是很有用。

但是,针对这个全局流程监视器仍然预定义了一些 KPI,主要是为了演示这个特性。它们显示在 图 24 中。前五个 KPI 的用处值得怀疑。它们显示了任何被监视的人工任务的平均总持续时间、等待持续时间和工作持续时间,以及在过去 7 天里已完成的人工任务的总数。“per workday” KPI 将过去 7 天完成的任务数除以 5,因此忽略了周末。然而,从技术和性能的角度来看,最后两个 KPI 是有用的。


图 24. 预定义的 KPI
预定义的                     KPI

查看图 23 的大图。

KPI 的众多出色特性的其中之一就是它们可以在任何时候被添加到一个已经部署的监视器上。因此,一旦知道应该对哪个流程测量某些关键性能指标,就可以为这个流程轻松地定义一个 KPI 和一个数据过滤器,并且将只计算有关这个流程的合计值。虽然一个通用的监视器模型显然无法以开箱即用的方式提供这种 KPI,但是可以很轻松地添加它们。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14789789/viewspace-664586/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/14789789/viewspace-664586/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值