分类:
2010-12-26 15:17:13
2.5 事件处理模式
通常,规则引擎具有一个众所周知的处理数据和规则的方法,并且把结果提供给应用程序。此外,有关事实应该如何被提交给规则引擎并没有太多要求,尤其因为通常情况下,处理本身是时间无关的。对多数场景这是一个好的假定,但并不是所有都是。在要求包含实时或接近实时事件时,时间就成为了推理过程的一个重要变量。
下面的章节会解释在规则推理中的时间影响,以及Drools提供的用于推理过程的两个模式。
2.5.1 云(Cloud)模式
Cloud处理模式是默认处理模式。规则引擎的用户是熟悉这种模式的,因为它的行为是与包含在前面的Drools版本中的任何纯正向链接规则引擎的行为完全相同。
当运行在Cloud模式时,作为一个整体,引擎了解工作内存中的所有事实,无论是正规事实还是事件。没有时间流的概念,尽管事件象平时一样有一个时间戳。换言之,尽管引擎知道一个给定的事件被创建了,例如,在2009年1月1日,09:35:40.767,引擎不可能确定事件是多“老”了,因为没有“现在”的概念。
在这种模式中,引擎通常会使用多对多模式匹配算法,使用规则约束找到匹配的元组,如平时一样激活并引发规则。
这种模式并不会对事实强加任何种类的其他要求,因此,例如:
l 没有时间概念,不要求时钟同步。
l 不要求事件顺序,引擎看事件象一个无序的云,引擎根据它们尝试匹配规则。
另一方面,因为没有要求,某些好处也是不可用。例如,在Cloud模式中,不可能使用滑动窗口,因为滑动窗口是基于“现在”概念的,而在在Cloud模式中没有“现在”的概念。
因为不要求事件顺序,所以引擎不可能确定什么时候事件不再匹配,以及没有事件的自动生命周期管理,即当事件不再匹配时,应用程序必须显式的收回它们,而且应用程序用相同的方法处理正规事实。
Drools的Cloud模式是默认执行模式,但是在任何情况下,象在Drools中的其他配置一样,通过设置一个系统属性,使用配置属性文件或使用API,也可以改变这种行为。相应的属性是:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.CLOUD );
等价的属性是:
drools.eventProcessingMode = cloud
2.5.2 流(Stream)模式
流模式是当应用程序需要处理事件流时选择的模式。它为常规处理增加一些共同要求,但是却启用了一整套让事件流处理更简单的功能。
使用流模式的主要要求有:
l 在每个流中的事件是有时序的,即在一个给定的流内部,第一个发生的事件必须是第一个被插入到引擎中的。
l 引擎会通过使用会话时钟在流之间强制执行同步,所以,虽然应用程序不需要在流之间强制执行时序,但是非时间同步流(non-time-synchronized streams)的使用可能导致一些意想不到的结果。
上面给出的要求满足了,应用程序可以使用下面的API启用流模式:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
或,等价的属性:
drools.eventProcessingMode = stream
在使用流模式时,引擎知道时间流的概念和“现在”的概念,即引擎根据从会话时钟读取的时间戳知道事件是多老了。这个特性允许为应用程序提供以下额外的功能:
l 滑动窗口支持。
l 自动事件生命周期管理。
l 在使用非模式(Negative Patterns)时,自动规则延迟。
所有这些功能在下面的章节中解释。
2.5.2.1 在流模式中的会话时钟角色
当引擎运行在云模式时,会话时钟仅被用于到达事件的时间戳,到达事件并没有一个先前定义的时间戳属性。但是,在流模式中,会话时钟承担了一个更为重要的角色。
在流模式中,会话时钟负责保持当前的时间戳,并且根据它,在事件的老化时、在多源同步流时、在时间表远期任务时,等等,引擎进行所有的时间计算。
要了解如何配置和使用会话时钟的实现,请查看文档有关会话时钟的部分。
2.5.2.2 在流模式中的非模式(Negative Patterns)
与云模式相比较,云模式的非模式(Negative Patterns)行为不同于流模式中的。在云模式中,引擎假定所有的事实和事件是提前知道的(没有时间流的概念)。因此,非模式(Negative Patterns)是立即被运算的。
当运行在流模式时,具有时间约束的非模式(Negative Patterns)可能要求引擎在激活一个规则前等待一个时间段。时间段由引擎用一种方法自动计算出来,用户不需要使用任何技巧来达到预期的效果。
例如:
例子 2.11 在匹配后,马上激活规则。
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( ) )
then
// sound the alarm
end
上面的规则,没有要求延迟规则的时间约束,因此,规则立即激活。另一方面,下面的规则,必须在激活前等待10秒,因为它需要花10秒用于激活洒水装置(sprinklers)。
例子 2.12 一条规则,由于时间约束,自动延迟激活。
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( this after[0s,10s] $f ) )
then
// sound the alarm
end
这种行为允许引擎在同时处理非模式(Negative Patterns)和时间约束时保持一致性。上面的与下面编写的规则是一样的,但不承担用户计算和显式编写适当的限期参数:
例子 2.13 使用显式期限(duration)参数的相同规则。
rule "Sound the alarm"
duration( 10s )
when
$f : FireDetected( )
not( SprinklerActivated( this after[0s,10s] $f ) )
then
// sound the alarm
end
2.6 滑动窗口
滑动窗口是一种方法,用于限定有趣事件范围作为一个属于一个不断移动窗口的整体。最常见的两种滑动窗口实现是基于时间的窗口和基于长度的窗口。
下面的章节会详细说明它们。
重要:滑动窗口只当引擎运行在流模式时可用。有关流模式如何工作的详情,请查看事件处理模式章节。
2.6.1 滑动时间窗口
滑动时间窗口允许用户编写规则,其将仅匹配在最近的X时间单元内发生的事件。
例如,如果用户希望只考虑发生在最近2分钟内发生的股票记号(Stock ticks),该模式是这样的:
StockTick() over window:time( 2m )
Drools使用 "over"关键字关联窗口到模式。
一个更精细的例子,如果用户希望在过去最近的10分钟从一个传感器中读取的平均温度高于阀值时用声音报警,该规则是这样的:
例子 2.14 整个时间窗口的累积值
rule "Sound the alarm in case temperature rises above threshold"
when
TemperatureThreshold( $max : max )
Number( doubleValue > $max ) from accumulate(
SensorReading( $temp : temperature ) over window:time( 10m ),
average( $temp ) )
then
// sound the alarm
end
引擎会自动丢弃任何超过10分钟的传感器阅读(SensorReading),并保持计算的平均一致。
2.6.2 滑动长度窗口
滑动长度窗口工作的方式与时间窗口相同,但丢弃事件是根据到达的新事件而不是时间流。
例如,如果用户希望只考虑发生在最近的10个IBM股票记号(Stock ticks),它们与多老是无关的,该模式是这样的:
StockTick( company == "IBM" ) over window:length( 10 )
正如你所见,模式是与上面章节的表示是相似的,然而不是使用了window:time 定义滑动窗口,而是使用了window:length。
使用上面章节中一个相似的例子,如果用户希望在过去最近的100阅读从一个传感器中读取的平均温度高于阀值时用声音报警,该规则是这样的:
例子 2.15 整个长度窗口的累积值
rule "Sound the alarm in case temperature rises above threshold"
when
TemperatureThreshold( $max : max )
Number( doubleValue > $max ) from accumulate(
SensorReading( $temp : temperature ) over window:length( 100 ),
average( $temp ) )
then
// sound the alarm
end
引擎只保持最近的100个阅读。
2.7 知识库分割
警告:这是一个试验功能,在将来会有所变动。
典型的Rete算法通常使用单线程被执行。如 Dr. Forgy在几次机会中证实一样,该算法本身是可以并行的。Drools实现的ReteOO算法通过规则库分割支持粗粒度并行。
当应用这个选项时,规则库会被分割成几个独立的分割,并且通过分割一个工作者线程池会被用来传播事实。该实现保证,对一个给定的分割至多有一个工作者线程被用来执行任务,但是在一个时间可能有多个分割是“活动”的。
这一切对用户都是透明的,但除了异步执行的所有工作内存的动作(插入/撤销/修改)。
重要:该功能启用了并行的LHS计算,但是没有改变规则引发的行为,即,根据冲突解决方案策略,规则将按顺序连续引发。
2.7.1 什么时候分割是有用的
知识库分割对特殊情况是一个非常强大的功能,但它不是一个普通情况的解决方案。要清楚是否这个功能对一个给定的情况有用,用户可以按照以下的清单:
1. 你的硬件包含多个处理器吗?
2. 你的知识库会话处理高容量的事实吗?
3. 你的规则的LHS计算开销高吗?(例如:开销高的“from”表达式)
4. 你的知识库包含成百或更多的规则吗?
如果上面的答案都是“yes”,那么这个功能将可能全面提高你的规则库的计算性能。
2.7.2 如何配置分割
要启用知识库分割,设置下面的选项:
例子 2.16 启用多线程计算(分割)
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MultithreadEvaluationOption.YES );
等价的属性是:
drools.multithreadEvaluation =
这个选项的默认值为"false"(禁用)。
2.7.3 多线程管理
Drools为用户提供了一个简单的配置选项,用于控制工作者线程池的大小。
要定义线程池的最大值,用户可以使用下面的配置选项:
例子 2.17 设置用于规则计算的最大线程数为5
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MaxThreadsOption.get(5) );
等价的属性为:
drools.maxThreads = <-1|1..n>
这个配置的默认值为3,而一个负数意味着引擎会按规则库中存在的分割尽可能繁殖线程。
警告:使用一个负数设置这个选项通常是危险的。所以,始终用一个合理的线程正数设置它。
2.8 事件的内存管理
重要:事件的自动内存管理,只当引擎运行在流模式时才被执行。有关流模式如何工作的详情,请查看事件处理模式章节。
引擎运行在流模式的好处之一是:当一个事件的由于时间约束可能不再匹配任何规则时,引擎可以侦测到它。当发生这种情况时,引擎可以无副作用从会话中安全地撤销事件,并释放该事件使用的资源。
有两种基本方法,用于引擎计算一个给定事件的匹配窗口:
l 显式地,使用到期策略。
l 隐式地,分析有关事件的时间约束。
2.8.1 显式到期偏移量
第一种方法,允许引擎计算一个给定事件类型感兴趣的窗口是通过显式设置它。要做它,只使用声明语句,并为事实类型定义一个到期:
例子 2.18 显式为StockTick事件定义一个30分钟的到期偏移量。
declare StockTick
@expires( 30m )
end
上面的例子显式为StockTick事件定义一个30分钟的到期偏移量。在这个时间之后,假定没有规则仍然需要该事件,引擎会终止并自动从会话中删除该事件。
2.8.2 推理到期偏移量
另外一个方法,引擎为一个给定事件计算到期偏移量是隐式,通过在该规则中的时间约束。例如,假设以下规则:
例子 2.19 使用时间约束的例子规则
rule "correlate orders"
when
$bo : BuyOrderEvent( $id : id )
$ae : AckEvent( id == $id, this after[0,10s] $bo )
then
// do something
end
分析上面的例子,只要一个 BuyOrderEvent匹配,引擎自动计算它,需要储存它长达10秒,等待匹配AckEvent。所以, BuyOrderEvent的隐式到期偏移量将会是10秒。另一方面,AckEvent,只能匹配现有的BuyOrderEvent,因此它的到期偏移量将会是0秒。
引擎将会对整个规则库做这种分析,并且为每个事件类型找出偏移量。只要一个隐式的到期偏移量与显式的到期偏移量相冲突,那么引擎会使用两个中的大者。