赞
踩
转自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. 或更高版本。
本文将详细地探究这个全局流程监视器模型的设计细节。本文主要有三个意图:
那些主要希望了解预置指示板中的数据的读者应当参考 [5]。但是,这份参考对于那些希望调整或定制全局流程监视器模型(以获得其内置功能概览)的开发人员来说也是一份不错的介绍性参考资料。本文假设您对监视器模型概念有充分的了解,并且熟悉监视器模型编辑器。您可以在 [1] 或 [2] 中找到有关这些主题的介绍。
图 1 展示了监视器模型的主要结构。
下面解释为什么会形成这样的结构:我们希望同时在定义级别(M1)和执行级别(M0)监视流程及其步骤和变量。一个流程定义可以具有许多步骤定义。它还可以具有多个执行。一个流程定义可以被分解为多个步骤定义,但是每个步骤执行也可以是一个步骤定义的一个逻辑子分支。因此,要在定义级别和执行级别同时监视流程及其步骤,理想的监视上下文(MC)结构就是如 图 2 所示的菱形。
但是 Monitor 的架构要求监视上下文形成一个树形结构。为了解决这个限制,我们应用了两项不同的技巧来分解这个菱形结构。它们在 图 1 中以红色表示:
这解释了 图 1 所示的模型结构的上半部分,这个部分目前显示为一个树形结构。下半部分显示了一个简单的两层结构,用于监视人工任务定义及其执行。由于 Process Server 允许将人工任务定义为一个流程中的步骤,或者定义为单独的任务,所以只跟踪流程的监视器模型将无法捕捉到全部的人工任务。因此,我们增加了模型的下半部分来解决那些独立的任务。然而,考虑到完整性,Task Definition 和 Task Execution MC 同时订阅了来自内联和独立人工任务的事件,这样就可以使用模型的上半部分以 “流程步骤” 的形式监视内联任务,而使用模型的下半部分监视 “人工任务” 形式的内联服务。
我们对监视器模型结构的介绍就此结束。还有许多其他的监视上下文定义,它们很大程度上都具有一些辅助作用:保存应当放在复杂指标类型中的数据。由于 Monitor 只支持简单的指标类型,因此必须使用附加的 MC 定义实现复杂的指标类型以及序列(数组)。几乎所有这些附加的 MC 都捕捉执行状态的历史(带有时间戳)、任务分配、任务升级、模型版本、实例迁移、变量值等等。
图 3 展示了全局流程监视器模型的完整的监视上下文结构,其中强调了主要的监视上下文定义。这些表示主要的导航路径,您在研究此模型时不应当忽略它们。监视上下文名称的一些部分使用括号括起,这表示显示名已被相应地缩写。
如果查看监视器模型的源代码,会发现一些名称以 “Aux” 开头的监视上下文定义,这些定义没有显示在 图 3 中;您还会从 图 3 所示的 MC 中发现名称以 “Aux” 开头的指标。所有这些都属于附加结构,用于支持监视器模型逻辑。在设置指示板视图时应当忽略它们,并考虑将它们隐藏起来(特别类似于面向对象编程中的私有字段)。
在深入探究细节之前,本节将简单介绍全局流程监视器模型的工作原理,我们将关注核心结构和主要逻辑,故意忽略了许多更细节的东西。这里的描述参考了 图 3。
监视器模型的核心设计假设流程引擎将发出以下三种事件:
它还假设有关流程步骤和流程变量的事件将携带一个流程执行标识符,并且所有此类事件都将引用流程定义(有时还会引用服务器,以区别运行在不同位置的同一流程的不同副本)。运行在 Process Server 上的 BPEL 流程将发出所有这三类事件([4])并满足这些标准。当超出这个范围后,我们注意到事件将以类似步骤事件的方式处理。
全局流程监视器模型被构建为对这三种事件类型都不使用强制方式。例如,如果一个流程只发出了变量更改事件,那么是否存在流程执行则是通过这些事件推断得出的;执行的开始和终止时间是未知的,但是将对最早和最迟变量更改事件做出一个估计;对流程步骤本身一无所知。另举一例,对于只发出有关某些人工任务的事件的流程,将按照下面的详细程度执行监视:不会提供有关自动化步骤、执行状态更改、变量值的信息,但是将监视人工任务(从其中接收事件)并推断它们的流程执行。
如何实现上述监视?从主干结构的最底层 MC 开始(图 3 所示的粗体名称):Step Execution 和 Process 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 Definition 和 Task Execution MC 的逻辑类似于 Process Definition 和 Process Execution MC 的逻辑。
Process Server 允许您有选择地启用和禁用审计事件。表 1 提供了一些技巧来确定应该对全局流程监视器启用哪些事件,以及一些基本原理。
要启用的事件 | 原因 |
---|---|
对于每一个将要进行监视的流程,在流程级别启用所有事件。 | 通常,一个流程仅在其生命周期内发出少数一些事件(开始、终止、故障、删除等等),因此这不会引起过度的事件通信量。启用所有事件将使 Monitor 全面地感知执行状态更改,这通常要比启用部分事件所带来的任何性能收益更重要。 |
对于每一个要监视的活动,同样启用所有事件。 | 通常,存在关联的活动为人工任务和调用。只启用部分事件,比如活动开始和活动终止,可以视为一种减少事件通信量的方式,但性能收益通常不太显著,而且无法弥补由于信息丢失带来的损失;一个更有效的减少事件通信量的方法是对所有短期运行的步骤、底层子流程、循环体内的步骤等禁用所有事件。 |
如果同时监视一个流程和它调用的子流程,那么至少要对链接两者的调用活动启用进入事件(entry event)。 | 这允许全局流程监视器捕捉调用者与被调用者之间的关系,如 跟踪调用链 中所述 |
对希望监视的人工任务(独立的和内联的)启用所有事件。 | |
对循环活动启用所有事件。 | 这将为您提供有关循环迭代的历史,并且带有时间戳。通常这足以监视循环执行,并找出时间消耗在了哪些循环上。它可能还允许您禁用循环体内的任意和所有的事件发出行为。 |
对短期运行的自动化步骤禁用事件。 | 这明显提升了性能,但是它还帮助避免指示板上出现不必要的混乱,因为这些短期运行的步骤常常与业务用户无关。 |
对您希望进行监视的流程启用(且仅对它们启用)变量更改事件。 | 另外,专门针对监视分配流程变量将是一个非常有用的技巧:为您希望监视的流程定义变量,并在流程的适当位置映射到这些变量(例如,当实现主要的目标或业务项目经历重要的状态更改)。监视这些变量可以为您同时提供业务级的内容、流程状态和时间线。 |
本文并不打算提供详细的介绍;我们不会遍历全局流程监视器的每个模型元素并进行详尽的介绍。相反,我们将重点关注该模型的几个典型结构并强调其设计的关键方面。一旦理解了这些结构,那么模型的其余部分对于任何查看它的人都会变得易于理解。另一个好处是这些重点介绍的结构可以供您在编写监视器模型时作为参考,让您能够理解可以完成哪些内容以及如何完成。
作为这个监视器模型的入站事件订阅的一个示例,表 2 展示了 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 声明了上述表达式中使用的名称空间前缀。这些声明将在整篇文章中使用。
前缀 | 名称空间名 | 使用 |
---|---|---|
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 | 全局流程监视器用户定义函数 |
标识符 | 路径 | 类型 |
---|---|---|
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 中有三个测量指标,即 Deployed At、Name 和 Identifier,仅由到达的第一个事件初始化,因此不需要继续向这个监视上下文发送任何事件。但是 Latest Audit Event 和 Earliest Audit Event 时间戳将被持续更新(只更新 Earliest Audit Event,因为这个监视器模型允许事件按任意顺序到达)。此外,Version 指标使用最新部署的流程版本更新(任何此前的版本都被记录到一个 Process Version 子 MC 中),最终,每一个新事件都将从零开始重新启动 Time Since Last Active 计时器(timer)。这个计时器用于在 90 天内没有任何事件到达时终止顶级监视上下文和所有后代上下文(流程随后被认为处于非激活状态)。要修改这个超时值,必须编辑 Process Definition MC 中定义的 Inactive 触发器的触发条件,但是您也许会考虑一个更复杂的实现,该实现可能会调用一个用户定义的函数作为触发条件的一部分,传递 Time Since Last Active 和 Name 以及 Deployed At 测量指标,并运行一些外部逻辑来确定此时是否应该终止上下文结构。这甚至会涉及到一个人类决策制定者。如果一个流程执行在其 Process Definition 监视上下文被终止后启动,那么一个新的顶级 MC 将被创建并且将会立即恢复监视。然而,统计数据将被区别对待:旧流程的统计数据将被放在已终止的 Process Definition 监视上下文中,而较新的流程的统计数据将被积聚在新 MC 中。
再次参考 表 2,并在监视上下文定义中向下移动,您将看到 Process_Definition/Process_Execution MC 具有两个事件订阅,State_Changed 和 Step_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/Called_Execution_Exists 订阅不会出现问题,因为它永远不会引起 MC 创建。
查看 表 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, ' ' ) ) |
再次涉及有关 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 |
因此,要捕捉 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 的算法利用了关联谓词的各个部分,如下所示:
一个新的监视上下文必须始终位于一个 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:dateTime 和 xs:duration 指标都可以作为 XML Schema 格式化字符串获得。例如,指标 Latest Audit Event (xs format) 和 Earliest Audit Event (xs format) 分别包含 Latest Audit Event 和 Earliest Audit Event 的 XML Schema 格式化表示。对指示板显示严格执行了这一操作 —— 并且应当成为一个格式化选项,可以通过在指示板上扭动开关来进行选择。目前,您可以在配置小部件时选择是否显示这两种类型指标的其中一种(或同时显示)。
显示 xs 格式化的字符串将可以展示更多信息,也就是说,可以显示毫秒和时区,并删除了空格来避免使用环绕(wrapaound)效果,这种效果会在表式视图中增加行高。在设置您的指示板时,选择非 xs 版本可以实现更加用户友好的格式,这将显示使用本地时区的时间戳并禁用毫秒,或者使用 xs 格式化的版本,这可以显示更详细的信息,实现在技术上和标准上更兼容的格式,并且可以在表式视图的每个屏幕中显示更多行。全局流程监视器模型附带的基本指示板使用默认格式,而高级指示板则显示 xs 格式化字段。
为了方便演示,图 5 中并列显示了两种格式的时间戳。可以看到,对于默认格式(第一列),没有显示时区信息,这样您必须知道所看到的屏幕截图是在哪里捕捉的,这样才能够了解它所表示的时间点。第一列还显示出任务 1 花费了 1 秒的时间从非激活状态转换到准备(ready)状态,但是第二列显示该时间只用了 0.187 秒。
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 ) ) ), ' |
首先,您看到了一个 if-then-else 表达式,这确保只有 PROCESS_CORRELATION_SET_INITIALIZED 事件(事件代码 42027)可以更新 Correlation Sets 指标。当 State Changed 订阅接收到一个此类事件时,将在 Correlation Sets 指标的前面添加下面的内容:(1) 针对 Monitor server 的本地时区进行了格式化的事件时间戳,(2) 一个换行符,(3) 格式化的关联集信息,(4) 最后是另一个换行符。substring 函数将结果的长度限制为 4000 个字符(字符串指标的最大大小),从而避免在所有关联集的总大小超出该限制时出现运行时异常,这种情况应该会很少见。如果超过一个关联集针对流程执行进行了初始化,那么该指标将显示所有的关联集,在每个关联集的前面添加了它自身的初始化时间戳。对于其他包含使用了时间戳的序列的指标,比如 State History、Escalation History 等等,使用子 MC 保存包含时间戳的值。对于关联集,这不是必要的,因为在绝大多数情况下,只有一个或数量很少的关联集。当然,如果您愿意的话,可以添加这些子 MC,使用用于 State History、Escalation 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 标记将被大部分 Web 浏览器忽略,因此将变为不可见。
如前所述,全局流程监视器模型为大量流程和人工任务定义了相对较少的事件订阅。如 表 2 所示,一个 Process Execution Step MC 实际上只有一个事件订阅 State Changed,它订阅了所有报告运行中的流程步骤的状态更改的事件。对于大部分下游处理 —— 触发器、计数器、映射等等 —— 这样做要比对需要进行报告的每种状态更改使用单独的订阅更方便(正如您从 表 2 看到的一样,实现了 34 种不同的事件代码):例如,Time Started、Time Ended、Name 等指标可以通过一个简单的映射进行更新,因为事件时间戳、步骤名等都位于每种事件的相同位置中。通过对所有状态更改事件使用一个订阅,一个映射就可以替代 34 种事件代码。
一个例外是 State 指标的映射(更准确地说,是指标 Aux Last State Reported,State 指标是从其中派生而来的),这个映射绝不简单。鉴于一些原因,我们将进行简单的介绍,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 监视上下文的 State 和 State History 指标背后的逻辑。Step Execution 和 Task Execution MC 的 State 和 State History 指标使用了相同的逻辑,并且类似的逻辑也被用于 Escalation History、Potential Owners History 和 Work 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 Timestamp 和 State 指标中。
使用这种逻辑的理由是处理无序到达的 State Change 事件。因为 Monitor 使用内置的逻辑来确保来自 Process Server 的事件具有合适的次序,因此没有必要监视运行在 Process Server 上的 BPEL 流程。然而,可能会添加没有提供事件序列标识符的事件源,并且为了演示通用的一般技巧,因此模型中保留了这种逻辑。
细心的读者可能会注意到,通过使用 测试可能为空的字段 中的建议,State Timestamp 和 State 的映射表达式可以被简化:
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 相同的状态历史。
可以清楚地看到这个指标如何引起过高的行高,这也是为什么这些时间戳历史被移到子监视上下文的原因之一。(另一个原因是能够捕捉长期运行的循环的状态历史)。您可以看到在 State History 指标的前面显示了指向子 MC 的下钻(drill-down)链接;向下钻取将引导您至 图 5 所示的视图。State History 和其他此类指标(Escalation History、Potential Owners History、Work Item Update History)都没有显示在全局流程监视器附带的指示板配置中,但是它们确实存在于模型中。可以在指示板配置中随意使用它们,而不是使用下钻链接,提供一个包含所有内容的单一页面来显示流程捕捉的执行状态。此外,State History 指标用于计算人工任务处于等待和工作状态的持续时间,我们将在 收集执行统计数据 中加以讨论。
使用下面的方式创建用于保存包含时间戳的状态记录的子 MC:当事件的 State 指标发生变化时,从 Step Execution MC 中发送出事件,然后由同一个监视器模型接收事件。(Task Execution MC)出站事件定义和相应的(Task Execution State History 子 MC)入站事件定义分别如 图 7 和 图 8 所示。
图 7 展示了由 4 个指标组成的这些事件的有效负荷:
子 MC 中的事件订阅过滤器(参见 图 8)将检查有效负荷中是否存在某个任务执行状态字段。它定义了相同的事件部分作为其对等的出站事件定义,名为 emptyString 的事件部分除外,我们将稍后讨论。
查看 图 8 中的关联谓词,您将看到前两个比较将对祖父 MC 键和父 MC 键与事件有效负荷中的对应字段进行比较,这是 MC 创建订阅的要求。最后一个比较的值始终为 false,但是满足关联谓词的正式条件,即对目标 MC(Time)中的指标与依赖于传入事件的表达式执行比较。emptyString 字段永远不会存在于一个传入的事件,并且将切换到一个 dummy xs:dateTime 值来防止关联谓词中与空字段有关的任何问题。当然可以使用其他的方式来构建比较,使其满足这些常规的要求,但始终得到 false 值:图 8 中的表达式就是这样一个例子。图 5 中看到的 Time 和 State 指标是根据事件中的相应字段设置的,而 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 中的 Version 和 Version No Blanks 指标始终包含由最后一个事件报告的版本(即当前运行的版本)。然而,流程定义的所有版本都由 Process Version 子监视上下文记录,这个上下文的指示板呈现类似于 图 9。
图 9 中的流程定义名是多余的(可以在父 MC 中查找),但是为了方便使用,对它进行了复制。版本可以显示空格,也可以不显示空格,但是大多数其他 MC 实例视图使用了不包含空格的格式,以避免在字符串被环绕为多个行时出现行高增高的现象。最后,将显示来自流程版本的最早的和最迟的审计事件的时间戳,用于确定处于活动状态的时间跨度。
流程模板版本由 Process Version MC 的事件订阅记录,如 图 10 所示。
Detected 事件订阅接收来自 BPEL 流程的任何事件,这个 BPEL 流程承载了一个流程定义名(processTemplateName)和一个版本标识符(processTemplateValidFrom)。关联谓词在父上下文中引用流程定义名和服务器(如果给出的话),并比较事件中的版本标识符与 Process Version 子 MC 中的版本标识符。如果没有发现匹配,即,如果这是第一个报告新流程版本的事件,那么将创建一个新 MC。否则,事件将被发送给这个版本的现有 MC,并用于更新 Earliest Audit Event 和 Latest Audit Event 时间戳。
从版本 7 开始,Process Server 允许运行中的流程执行在不同流程定义版本之间迁移。此类迁移涉及一些事件,这些事件将由全局流程监视器接收并用于报告这些迁移。 有两种 MC 定义与流程实例迁移相关:Process Migration 和 Process 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 |
您还可以看到最早和最迟迁移的开始和结束时间。
图 12 展示了已迁移的流程执行的 5 个 Process Execution MC 的屏幕快照。在第二列显示的版本 Wed_2009-12-16_00:00:00.000 是迁移的目标版本。可以看到这些执行在 Process Execution Migration 列中被迁移。单击下钻链接以查看每一个迁移的详细信息。
图 13 展示了通过 图 12 的链接下钻到 Process Execution Migration MC 的结果。您将看到流程定义的名称、旧的和新的版本、旧的和新的流程定义标识符,以及发生迁移所持续的时间间隔。通过上钻到 Process Execution 级并再次下钻到 Process Execution Steps,您可以看到执行在此刻所处的位置。
总的来说,全局流程监视器将按照以下方式跟踪流程执行迁移:
Process Migration 和 Process Execution Migration MC 的逻辑非常简单:接收报告迁移开始或结束的事件,并用之创建或更新它们的目标 MC。需要注意的是,Process Migration 或 Process Execution Migration 监视上下文的关联键包含一对儿流程定义标识符,表示流程定义的 “源” 和 “目标” 版本。
BPEL 流程可以定义事件处理器,这个事件处理器可以与整个流程关联,也可以与某个范围关联,并且由传入的流程事件激活。流程事件是在流程执行中单向传递的消息,不会与流程执行发出的审计事件混淆。“流程事件” 一词用于那些入站单向消息。全局流程监视器将使用 Process Execution Event MC 跟踪流程事件,这个 MC 表示由与其父 MC 对应的流程执行接收的流程事件。
图 14 显示了审计事件的事件订阅,审计事件将报告在运行执行时到达的流程事件。对于每个接收到的流程事件,只要求具有一个审计事件,从 zero|one|multiple 匹配的关联设置 create|ignore|error 中可以看到这一点。报告流程事件的 MC 使用事件时间戳作为键,前提条件是一个流程执行不会在完全相同的时间报告接收到两个流程事件。
对于接收自流程级别的事件处理器的审计事件和接收自范围级别的事件处理器的审计事件,有多种不同的订阅。这是因为由这两种事件处理器发出的审计事件截然不同。事实上,它们非常不同,以至于会定义两种类型的 MC,一种用于流程级别的事件处理器,一种用于范围级别的事件处理器。表 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 展示了为了实现此目的而添加的指示板视图。
这个指示板视图仅仅是对 Process Execution MC 的 Instances 视图做了不同的布局。在这里,没有根据流程定义对执行进行过滤,相反,根据初始的(顶级)流程执行进行分组,而在每一个组的内部根据起始时间进行排序。您可以在 图 15 中看到两个顶级执行,标识符分别为 PI:90030125.9d7f4954.90e5c5f6.3c30ce6 和 PI: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 中的第 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 个或多个项,并且将发送出站事件的相应编号:每一个此刻已知的子流程执行都有一个编号。
在结束本节时,需要注意一下 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 Definition 和 Step Definition MC 中找到。图 17 展示了为总的流程持续时间计算的 5 个合计(最小、最大、总和、平均、标准偏差),以及实现这些计算所涉及的模型元素。
下面概况了如何合计流程执行的总持续时间,以及在重启执行时如何重新调整。步骤编号对应于 图 17 中的编号。
Total_Duration |
Time_Started |
Has_Ended |
Aux_Has_Ended_At_Least_Once |
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_Candidate_Max_Total_Durations |
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 |
可以看到重新启动逻辑(通过 Has Ended 再次变为 false 后发起,这将触发 Aux Restarted)重新建立了状态,之后流程执行出现一个异常:指标 Aux Has Ended At Least Once 没有被重新设置为 false;当然,这是故意这样做的。
本节展示的技巧用于计算流程执行的总持续时间的计数、最小值、最大值、总和、平均值和标准方差,这些技巧可以被广泛地使用,以获得一个父(或更高级)监视上下文中的子指标值的合计(参见 在祖先监视上下文中计算指标的合计值)。
以上用于计算流程执行的总持续时间的总和、平均值、最小值、最大值和标准方差的算法还被用于在流程定义级别计算等待持续时间和工作持续时间的合计值。此外,这些技巧还用于计算流程步骤和人工任务在其定义级别的总持续时间、等待持续时间和工作持续时间的合计值 —— 重试操作现在起到重新启动流程执行的作用,并触发必要的调整。
为了进行全面的观察,图 18 展示了 Aux Update Aggregate Durations 触发器在 Process Execution MC 中的所有下游影响(图 17 中的 #9)。用于更新总持续时间的模型元素(见 图 17 和上文的讨论)使用红色标记。
计算监视上下文内的子 MC 的数量通常都很有用。例如,流程执行的步骤的数量等于其 Process Execution MC 的 Process Execution Step 子 MC 的数量,而其变量的数量等于其 Process Execution Variable 子 MC 的数量。理想情况下,要获得这些计数,应当能够在 Process Execution MC 中定义一个指标,且映射类似于
VariablesMonitor 中的当前 XPath 2.0 支持并不允许这样做;然而,您可以在一个父 MC 中定义一个计数器,它将在每次创建一个子 MC 时加一。在决定如何控制这个计数器时,必须区分两种情况:如果引起子 MC 创建的订阅最多接收一个事件,那么它们可以用于在父 MC 中直接对计数器加一。但是如果这些订阅在一个子 MC 创建后继续接收事件,那么必须使用一个初始化触发器:该触发器没有条件,不可重复,并且链接到所有创建子上下文的事件订阅。注意链接可以是间接的,方法就是让触发器依赖于每个创建事件都将更新的指标(例如 MC 键)。
图 19 展示了一个初始化触发器的例子。当 Accessed 订阅接收到有关流程执行变量的第一个事件时,一个新的 Process Execution Variable 上下文将被创建,触发器 Aux Detected 将被计算,由于它没有包含条件,因此将触发。由于该触发器是不可重复的,并且它的条件始终为 true,因此不管随后接收到多少 Accessed 事件,都不会再触发它。触发器将在 Process Execution 父监视上下文(图 19 中未显示)中对 Variables 计数器加一。
通过刚才描述的方法,就可以计算已经被初始化的子 MC 的数量。要计算当前活动的子 MC 的数量,子 MC 的任何终止触发器都必须对计数器减一。计算子 MC 总结了这些过程,并且可以使用相同的方法来在多个后代级别上计算 MC。
对于人工任务,全局流程监视器将记录三个持续时间:
考虑 图 20 所示的状态历史。
这个任务执行由用户 admin 声明,随后取消任务分配,然后再由该用户声明,最后结束。因此,该任务在两个时间间隔内等待用户声明它,这两个阶段都构成了等待持续时间。在这两个时间间隔内,任务还处于被声明状态(工作状态),这又构成了工作持续时间。研究 图 20 所示的视图,我们来看一下对这个任务执行的总结,如 图 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 展示了两个 Process Execution MC,每个 MC 拥有一个 Process Execution Step 子 MC。位于上部的 MC 表示发出调用的流程执行(父流程),其子 MC 表示调用步骤。位于下方的 MC 表示被调用的流程执行(子流程),其子 MC 表示这个流程内的一些人工任务。
下面简单描述发生的操作:将从人工任务的状态历史计算它的等待/工作持续时间,如 计算状态持续时间 所述。这些持续时间将在流程执行的级别上累加,同时还包括这个流程执行中的所有人工任务和调用的等待/工作持续时间。每当这些合计值发生变化时,一个内部事件将从表示流程执行的 MC 发送到表示其调用的 MC 中(图 22 中的箭头),报告新的累计持续时间。调用的等待/工作持续时间将从事件中设置。将在(调用流程的)流程级别上累加,同时包含该流程执行内的所有人工任务和调用的等待/工作持续时间。
下面更详细地描述了这些步骤,其中使用了 图 22 的数字编号:
Working_Duration |
Working_Duration |
使用完全相同的逻辑计算等待持续时间的累计值。
注意,这里使用一个内部事件来在堆栈内向上填充信息,从表示被调用的流程执行的 MC 开始,一直到表示其调用方的 MC。将其与 跟踪调用链 中描述且在 图 16 中显示的逻辑相比较,其中,内部事件被用于在堆栈中向下传递信息。
最后,请注意具有并行特性的流程,流程执行的工作或等待持续时间会很容易超出它的总的(流逝的)持续时间,同样的事情也会发生在流程定义级别:比如,流程定义的平均等待持续时间会超出它的平均总持续时间。
通常有两种技巧可用于处理那些无序到达的事件,这两种技巧在 处理无序事件 中做了总结,下面对它们进行了解释。
这里给出有关第一个技巧的例子。作为一种计算任务持续时间的简单方法,您可能会在一个监视器模型中编写以下逻辑(Start_Time 和 Duration 为指标;Task_Started 和 Task_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 Duration 和 Aux Receive Called Execution Working Duration 事件订阅。如果针对它们所订阅的事件的关联失败了,那么这意味着来自子流程调用步骤的事件还没有到达(而来自子流程执行的事件已经到达),或者是表示子流程调用步骤的 MC 还没有接收到一个包含被调用的执行的标识符的事件。随着越来越多的事件到达,应当恰当地处理这两种情况,因此,选择了一个重试选项来处理这种情况下出现的混乱事件顺序。
注意,计时表(定时器)对于那些无序到达的事件尤其敏感:如果启动时间在完成事件之后到达,显然结果是没有意义的。因此,与体育赛事中的计时表不同,监视器模型计时器不应该用于计算两个事件之间的时间(参见 使用计时表(计时器))。
在超过一个预定义的持续时间后仍没有接收到一个心跳信号或其他预期响应时需要发出警告,这时计时器就派上用场了。当等待时间开始后可以启动计时器,并且一个循环触发器将不断比较计时器与阈值持续时间。当计时器超出定义的阈值后,触发器将触发,并且将发出一个警告。其他使用计时器的例子包括任何需要在指示板上显示运行中的时钟的场景。
如果使用第一种方法处理无序到达事件(即所有事件被接收,保存了有效负荷数据,并且下游操作被阻塞,直到所有所需的信息变为可用),那么会产生一个问题,即任何这些事件都必须创建目标 MC(如果不存在的话)。如果每个事件都包含了目标 MC 或任何祖先 MC 的必需的键,那么就可以使用这个策略。全局流程监视器模型就是按这种方式编写的。如果不能实现每个事件都创建 MC 的话,那么应当考虑重试选项:如果事件到达后尚不存在任何目标 MC,那么事件将被延迟,直到创建 MC 的事件到达。
注意:
现在您应当很清楚,未作修改的全局流程监视器模型可能并不适合那些性能非常关键的环境。一个传入事件可能会引起许多个次要事件,这是一个严重影响性能的因素,但是某些情况下,可以很轻松地处理它。在不影响模型的任何关键功能的情况下,许多内部事件都可以被禁用。
下面是一些性能调优技巧:
最后需要记住的是,通常可以通过截断事件源来获得大量的性能:对任何以及所有不需要进行监控的流程步骤、流程变量甚至完整子流程禁用事件发出。
表 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 上方的图表显示了针对三种不同流程定义的已经启动的流程执行的数量(橙色),正在处理中的流程执行的数量(绿色),以及已经完成的流程执行的数量(蓝色)。这个视图使用了一个基于 Process Definition 监视上下文的多维数据集(cube),一个基于流程定义名的维度,以及三个指标 Started、In Progress 和 Finished,这三个指标反映了 Process Definition MC 中的同名计数器的总和。由于每个维度值只有一个 Process Definition MC(除非有多个重复的流程名),因此不会计算总和,并且只会得到这些计数的图形化呈现。
图 23 下方的图表显示了流程执行的总持续时间、等待持续时间和工作持续时间的最大值、平均值和最小值。流程定义(显示了其执行时间的总和)使用下拉菜单选择(选择了 ProcessCustomerOrder)。这个视图使用了一个基于 Process Execution MC 的多维数据集,一个基于流程名的维度以及 9 个指标。这些指标表示具有相同流程名的所有流程执行的总持续时间、等待持续时间和工作持续时间的合计值。注意,这些相同的合计值被保存在 Process Definition MC 的指标中,并按照 收集执行统计数据 中的描述递增。您可以使用由多维数据集引擎计算的指标来验证通过监视器模型逻辑计算的 Process Definition 指标;它们必须匹配。
为了使您对预定义的维度模型有一个了解,表 7 展示了 7 个多维数据集中的其中两个。这些多维数据集也为 图 23 的图表提供了数据。
CUBE: Process Definition Cube | ||
---|---|---|
MC: Process Definition | ||
Measure | Source Metric/Counter/Timer/Key | Aggregation 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 / Levels | Source Metric/Counter/Timer/Key | |
Process Definition Id / Identifier | Identifier (M) | |
Process Definition Name / Name | Name (M) | |
CUBE: Process Execution Cube | ||
MC: Process Execution | ||
Measure | Source Metric/Counter/Timer/Key | Aggregation 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 / Levels | Source 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 Default 和 Time Ended With Default 来实现此目的,并赋予它们默认值(对 Time Started With Default 使用较早的时间点,对 Time Ended With Default 使用较迟的时间点)。一旦设置它们后,您将通过复制映射从实际的 Time Started 和 Time Ended 指标中重写它们。
虽然维度模型非常全面,但是 KPI 模型并不是这样的。如 收集执行统计数据 中所述,主要原因在于 KPI 被 Monitor 定义为一些独立的合计数字,没有在监视上下文中被实例化,而有用的合计值几乎总是 “流程定义” 或 “任务定义” 级别的。了解任何流程的平均执行时间,或者是任何任务的最小和最大工作时间,通常不是很有用。
但是,针对这个全局流程监视器仍然预定义了一些 KPI,主要是为了演示这个特性。它们显示在 图 24 中。前五个 KPI 的用处值得怀疑。它们显示了任何被监视的人工任务的平均总持续时间、等待持续时间和工作持续时间,以及在过去 7 天里已完成的人工任务的总数。“per workday” KPI 将过去 7 天完成的任务数除以 5,因此忽略了周末。然而,从技术和性能的角度来看,最后两个 KPI 是有用的。
KPI 的众多出色特性的其中之一就是它们可以在任何时候被添加到一个已经部署的监视器上。因此,一旦知道应该对哪个流程测量某些关键性能指标,就可以为这个流程轻松地定义一个 KPI 和一个数据过滤器,并且将只计算有关这个流程的合计值。虽然一个通用的监视器模型显然无法以开箱即用的方式提供这种 KPI,但是可以很轻松地添加它们。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14789789/viewspace-664586/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/14789789/viewspace-664586/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。