版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:forge(作者的Blog:http://forge.iteye.com)
原文:http://forge.iteye.com/blog/571047
Drools Fusion Features
Fusion 是业务逻辑集成平台(Business Logic Integration Platform)一部分,是一个CEP/ESP引擎。
事件,是指在应用程序域中有意义状态改变的记录
一个复杂事件是简单事件的集合。CEP(Complex Event Processing) 处理复杂事件-从事件云中检测并选择事件,根据选择的事件和他们建立的关系发现他们的关系推理出新的数据。
比如,一系列的巨额提款激发可疑交易事件的发生。一个复杂事件的发生是由一系列简单事件的引导形成的。
ESP(Event Stream Processing) 是更实时(real-time)的大量事件处理。例如,根据时间计算实时平均交易量。
一、事件(Events).
从drools视图来看,事件就是一个特殊的事实(fact)。我们说,所有的事件都是事实,但是所有的事实并不都是事件。
1.事件和事实不同特征:
(1)通常不可变:事件是在应用程序域中状态改变的记录,记录已经发生的事情,可变的事件是没有意义的。
应用程序允许给没有赋值的事件属性赋值,但是已经赋值的属性是不应该被改变的。
(2)强时间约束:规则涉及的事件通常需要多个事件的相互关系,尤其某个事件相对其他事件发生的时间点的时间关系。
(3)可管理的生命周期:由于上两个事件特征的原因,事件通常在一个有限的时间窗(Time Window)匹配事件和事实,
这就让引擎管理事件的生命周期成为可能。一个事件插入working memory,引擎有能力发现一个不在匹配
其他事实的事件,然后自动回收它,释放相关的资源。
(4)滑动窗( sliding windows)的使用:由于所有的事件都有关联的时间戳,可以定义并使用滑动窗,让事件在特定的一段时间有效。
2.事件声明:
import some.package.StockTick
declare StockTick
@role( event )
end
将一个已有的StockTick(类)事实生命为事件类型
@role 元数据接受2个值:
fact:默认值,声明为常规的事实
event:声明为事件
由于Drools支持在DRL中声明一个事实,可以去掉StockTick(类),还可以这样:
declare StockTick
@role( event )
datetime : java.util.Date
symbol : String
price : double
end
3.事件元数据
所有的事件都有一个事件元数据集合。当事件插入working memory,事件元数据会自动赋予默认值,我们可以用元数据标签改变他们的默认值。
假设我们有这样的一个实体类:
/**
* A class that represents a voice call in
* a Telecom domain model
*/
public class VoiceCall {
private String originNumber;
private String destinationNumber;
private Date callDateTime;
private long callDuration; // in milliseconds
// constructors, getters and setters
}
(1) @timestamp
默认的,当事件插入working memory,会读取(会话时钟)Session Clock的timestamp赋值给事件的startTimestamp。
@timestamp( <attributeName> ) //以毫秒计算
表示把事件的attributeName属性值赋给事件元数据startTimestamp
declare VoiceCall
@role( event )
@timestamp( callDateTime )
end
(2) @duration
Drools支持时间点(point-in-time)事件和时间间隔(interval-based)事件,一个时间点事件可以理解为时间间隔为0。默认,所有的事件的持续时间(duration)为0.我们可以通过@duration标签来修改事件的duration属性值。
@duration( <attributeName> )// 以毫秒计算。
表示把事件的attributeName属性值赋给事件元数据duration
declare VoiceCall
@role( event )
@timestamp( callDateTime )
end
(3)@expires
这个标签在引擎为Stream模式下才有效。默认的,当一个事件不在匹配(match)事实,激活规则(有活化的规则),事件自动失效(回收).
@expires( <timeOffset> )
显示的定义事件的失效时间。
timeOffset可以是下列形式:
[#d][#h][#m][#s][#[ms]]
[]表示是可选参数
declare VoiceCall
@role( event )
@timestamp( callDateTime )
@duration( callDuration )
@expires( 1h35m )
end
VoiceCall事件在1小时35分钟后过期。
二、会话时钟(Session Clock)
Drools 5提供2种时钟。默认的是基于系统时间的即时时钟(real time clock)。另一个是虚拟(pseudo clock)时钟,可以手动控制。
1. 即时时钟(Real Time Clock)
虽然Drools默认使用即时时钟,我们仍然可以显示的配置:
KnowledgeSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
config.setOption( ClockTypeOption.get("realtime") );
2. 配置虚拟时钟(Pseudo Clock)
KnowledgeSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
config.setOption( ClockTypeOption.get("pseudo") );
怎样控制虚拟时钟的例子:
KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
conf.setOption( ClockTypeOption.get( "pseudo" ) );
StatefulKnowledgeSession session = kbase.newStatefulKnowledgeSession( conf, null );
SessionPseudoClock clock = session.getSessionClock();
// then, while inserting facts, advance the clock as necessary:
FactHandle handle1 = session.insert( tick1 );
clock.advanceTime( 10, TimeUnit.SECONDS );
FactHandle handle2 = session.insert( tick2 );
clock.advanceTime( 30, TimeUnit.SECONDS );
FactHandle handle3 = session.insert( tick3 );
新的SessionPseudoClock时间值为0毫秒,new Date(0) 为GMT 1970 年 1 月 1 日 00:00:00 ,
clock.advanceTime( 10, TimeUnit.SECONDS ); SessionPseudoClock时间值+10秒。
三、Entry Points
一个entry point是事实(facts)或者事件(events)进入引擎的入口点。
1.声明Entry Points
rule "authorize withdraw"
when
WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream"
CheckingAccount( accountId == $ai, balance > $am )
then
// authorize withdraw
end
只有账户的余额大于提款请求的金额,规则才被激活
2.使用Entry Points
// create your rulebase and your session as usual
StatefulKnowledgeSession session = ...
// get a reference to the entry point
WorkingMemoryEntryPoint atmStream = session.getWorkingMemoryEntryPoint( "ATM Stream" );
// and start inserting your facts into the entry point
atmStream.insert( aWithdrawRequest );
通常情况下,我们不需要硬编码把事实插入到WorkingMemory中,通过Drools pipeline,
将从JMS或者 其他非java对象的数据源 中获得信息(对象)插入到WorkingMemory。
默认的entry point是"DEFAULT",是事实进入WorkingMemory的入口点。
session.insert(fact)实际上是session.getWorkingMemoryEntryPoint("DEFAULT").insert(fact)。
四、时间推理(Temporal Reasoning)
Drools 实现了由Allen定义的13种时间操作运算
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
Temporal Operators ┃ Illustration ┃ Interpretation
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x before y ┃ ___y___ ┃ X发生在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___y___ ┃
x after y ┃ ___x___ ┃ X发生在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x meets y ┃ ___y___ ┃ x结束时y开始
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___y___ ┃
x metby y ┃ ___x___ ┃ y结束时x开始
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x overlaps y ┃ ______y______ ┃ x开始在y之前,结束在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x overlappedby y ┃ ______y______ ┃ x开始在y之后,结束在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x starts y ┃ ______y______ ┃ x和y同时开始,结束在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x startedby y ┃ ___y___ ┃ x和y同时开始,结束在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x during y ┃ ______y______ ┃ x发生在y期间
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x includes y ┃ ___y___ ┃ y发生在x期间
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x finishes y ┃ ______y______ ┃X开始在y之后,同y一起结束
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x finishedby y ┃ ___y___ ┃X开始在y之前,同y一起结束
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x coincides y ┃ y ┃ X和y同时发生
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
1.before
$eventA : EventA( this before[ 3m30s, 4m ] $eventB )
也就是:
3m30s <= $eventB.startTimestamp - $eventA.endTimeStamp <= 4m
before操作符的时间间隔距离是可选的:
如果2个值都被定义了,间隔开始于第1个值,结束于第2个值。
如果只有1个值被定义,间隔开始于该值,结束于正无穷大。
如果没有值定义,间隔开始于1毫秒,结束于正无穷大。
时间间隔距离也可以是负数的,例:
$eventA : EventA( this before[ -3m30s, -2m ] $eventB )
注意:当时间间隔第1个值大于第2个值时,引擎会自动的调换他们。
2.After
$eventA : EventA( this after[ 3m30s, 4m ] $eventB )
也就是:
3m30s <= $eventA.startTimestamp - $eventB.endTimeStamp <= 4m
after操作符的时间间隔距离是可选的:
如果2个值都被定义了,间隔开始于第1个值,结束于第2个值。
如果只有1个值被定义,间隔开始于该值,结束于正无穷大。
如果没有值定义,间隔开始于1毫秒,结束于正无穷大。
时间间隔距离也可以是负数的,例:
$eventA : EventA( this after[ -3m30s, -2m ] $eventB )
注意:当时间间隔第1个值大于第2个值时,引擎会自动的调换他们。
3.meets
$eventA : EventA( this meets $eventB )
也就是:
abs( $eventB.startTimestamp - $eventA.endTimestamp ) == 0
meets操作符有1个可选参数
$eventA : EventA( this meets[ 5s ] $eventB )
也就是:
abs( $eventB.startTimestamp - $eventA.endTimestamp) <= 5s
注意:时间间隔不能为负
4.metby
$eventA : EventA( this metby $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.endTimestamp ) == 0
metby操作符有1个可选参数
$eventA : EventA( this metby[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.endTimestamp) <= 5s
注意:时间间隔不能为负
5.overlaps
$eventA : EventA( this overlaps $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp
overlaps操作符有1个/2个可选参数
$eventA : EventA( this overlaps[ 5s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp &&
0 <= $eventA.endTimestamp - $eventB.startTimestamp <= 5s
$eventA : EventA( this overlaps[ 5s, 10s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp &&
5s <= $eventA.endTimestamp - $eventB.startTimestamp <= 10s
6.overlappedby
eventA : EventA( this overlappedby $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp
overlappedby操作符有1个/2个可选参数
$eventA : EventA( this overlappedby[ 5s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp &&
0 <= $eventB.endTimestamp - $eventA.startTimestamp <= 5s
$eventA : EventA( this overlappedby[ 5s, 10s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp &&
5s <= $eventB.endTimestamp - $eventA.startTimestamp <= 10s
7.starts
$eventA : EventA( this starts $eventB )
也就是:
$eventA.startTimestamp == $eventB.startTimestamp &&
$eventA.endTimestamp < $eventB.endTimestamp
starts操作符有1个可选参数
$eventA : EventA( this starts[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 5s &&
$eventA.endTimestamp < $eventB.endTimestamp
注意:时间间隔不能为负
8.startedby
$eventA : EventA( this startedby $eventB )
也就是:
$eventA.startTimestamp == $eventB.startTimestamp &&
$eventA.endTimestamp > $eventB.endTimestamp
starts操作符有1个可选参数
$eventA : EventA( this starts[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 5s &&
$eventA.endTimestamp > $eventB.endTimestamp
注意:时间间隔不能为负
9.during
$eventA : EventA( this during $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp <= $eventA.endTimestamp < $eventB.endTimestamp
during操作符有1个/2个/4个可选参数
$eventA : EventA( this during[ 5s ] $eventB )
也就是:
0 < $eventA.startTimestamp - $eventB.startTimestamp <= 5s &&
0 < $eventB.endTimestamp - $eventA.endTimestamp <= 5s
$eventA : EventA( this during[ 5s, 10s ] $eventB )
也就是:
5s <= $eventA.startTimestamp - $eventB.startTimestamp <= 10s &&
5s <= $eventB.endTimestamp - $eventA.endTimestamp <= 10s
$eventA : EventA( this during[ 2s, 6s, 4s, 10s ] $eventB )
也就是:
2s <= $eventA.startTimestamp - $eventB.startTimestamp <= 6s &&
4s <= $eventB.endTimestamp - $eventA.endTimestamp <= 10s
10.includes
$eventA : EventA( this includes $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp <= $eventB.endTimestamp < $eventA.endTimestamp
includes操作符有1个/2个/4个可选参数
$eventA : EventA( this includes[ 5s ] $eventB )
也就是:
0 < $eventB.startTimestamp - $eventA.startTimestamp <= 5s &&
0 < $eventA.endTimestamp - $eventB.endTimestamp <= 5s
$eventA : EventA( this includes[ 5s, 10s ] $eventB )
也就是:
5s <= $eventB.startTimestamp - $eventA.startTimestamp <= 10s &&
5s <= $eventA.endTimestamp - $eventB.endTimestamp <= 10s
$eventA : EventA( this includes[ 2s, 6s, 4s, 10s ] $eventB )
也就是:
2s <= $eventB.startTimestamp - $eventA.startTimestamp <= 6s &&
4s <= $eventA.endTimestamp - $eventB.endTimestamp <= 10s
11.finishes
$eventA : EventA( this finishes $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishes操作符有1个可选参数
$eventA : EventA( this finishes[ 5s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 5s
注意:时间间隔不能为负
12.finishedby
$eventA : EventA( this finishedby $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishedby操作符有1个可选参数
$eventA : EventA( this finishedby[ 5s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 5s
注意:时间间隔不能为负
13.coincides
$eventA : EventA( this coincides $eventB )
也就是:
$eventA.startTimestamp ==$eventB.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishedby操作符有1个/2个可选参数
$eventA : EventA( this coincides[15s] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 15s &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 15s
$eventA : EventA( this coincides[15s, 10s] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 15s &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 10s
注意:时间间隔不能为负
五、事件处理模式(Event Processing Modes)
Drools支持2种事件处理模式:云模式(Cloud Mode)和流模式(Stream Mode)
1.云模式(Cloud Mode)
云(Cloud)处理模式是默认的处理方式。
在云模式下,不会区分事实和事件,都看成是事实。
(1)没有时间的概念。尽管事件在插入引擎被赋予了时间戳,也不能判断该事件“多大了”,因为没有“现在”的概念。滑动窗(sliding windows)基于“现在”的概念,所以在云模式下无法使用。
(2)无序的事件云。由于事件无序,没有自动的生命周期管理,需要像正常的事实一样显示的删除事件。
云模式虽然是默认的执行模式,我们也可以配置它:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.CLOUD );
等同系统属性配置:
drools.eventProcessingMode = cloud
2.流模式(Stream Mode)
当处理事件流的时候需要选择流处理模式。
在流模式下:
(1) 插入到引擎里的事件必须是时间顺序的。
(2) 引擎强制性的和使用的会话时钟session clock同步。
配置流模式:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
等同配置系统属性:
drools.eventProcessingMode = stream
使用流(STREAM)模式,引擎有时间流和"现在"的概念(通过读取Session Clock的时间戳),
提供了以下3种支持:
(1) 滑动窗的支持
(2) 自动的时间生命周期管理
(3) 使用消极模式(Negative Patterns)自动的规则延迟
3.会话时钟(Session Clock)在流模式(Stream mode)中的作用
在云模式下,会话时钟只有一个作用,就是给插入到working momery 的事件赋予时间戳的值(如果规则没有定义时间戳属性)
在流模式下,会话时钟负责维护当前时间戳,基于当前的时间戳,引擎根据事件的年龄计算所有时间运算,从多种源同步流,安排未来任务等等。
4.流模式(in Stream Mode)中的消极模式(Negative Patterns)
消极模式在流模式和云模式意义是不同的。
在云模式下,所有的事实和事件都是预先知道的,消极模式立即计算执行
//a rule that activates immediately upon matching
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( ) )
then
// sound the alarm
end
在流模式下,带有时间约束的消极模式可以要求引擎等待一段时间后激活规则。
//a rule that automatically delays activation due to temporal constraints
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( this after[0s,10s] $f ) )
then
// sound the alarm
end
六. 滑动窗(Sliding Windows)
滑动窗是一种选择事件范围的方式。
滑动窗有2种实现方式:滑动时间窗(Sliding Time Windows),滑动长度窗(Sliding Length Windows)。
注意:滑动窗只有在引擎为(STREAM)流模式下才是可用的
1.滑动时间窗(Sliding Time Windows)
滑动时间窗(允许我们的规则)仅仅匹配发生在最近X时间单元的事件。
例如,如果只关心最近2分钟的股票行情,模式(pattern)可以这样写:
StockTick() over window:time( 2m )
更复杂的例子:
如果最近10分钟从传感器读来的温度高于最大的临界值,发出警报。
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
2.滑动长度窗(Sliding Length Windows)
滑动长度窗(允许我们的规则)仅仅匹配发生在最近X个的事件。
例如,如果只关心最近10个IBM股票的行情,模式(pattern)可以这样写:
StockTick( company == "IBM" ) over window:length( 10 )
更复杂的例子:
如果最近100次从传感器读来的温度高于最大的临界值,发出警报。
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
七.Knowledgebase分割(Partitioning)
(注意:这是个实验性的特征,将来可能会发生变化)
传统的Rete算法通常一个线程在执行,也是Drools默认的。但是,该算法本身是可平行(化)的。Drools执行的ReteOO算法通过Knowledgebase分割支持粗粒度的平行执行。
当这个选项可用,Knowledgebase分割成若干个独立的区域,然后用线程池通过这些区域传播事实。该实现保证最多有一个线程在给定的一个区域执行。
要点:这个特征只在LHS平行执行可用,不会改变规则激活行为。
1.平行执行在什么时候有益:
(1)多处理器
(2)knowledge session处理大量的事实
(3)规则的LHS计算量大
(4)knowledge base包含数百或者更多的规则
如果以上条件都符合,这个特征可能会提高knowledgebase计算总性能。
2.配置Knowledgebase分割
//enabling multithread evaluation (partitioning)
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MultithreadEvaluationOption.YES );
等同系统属性配置:
drools.multithreadEvaluation = <true|false>
3.多线程管理
配置线程池:
//setting the maximum number of threads for rule evaluation to 5
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MaxThreadsOption.get(5) );
等同系统属性配置:
drools.maxThreads = <-1|1..n>
配置默认值是3,负数值表示在Knowledgebase中有多少分区,引擎就会产生多少线程,使用负数值是很危险的。
八.事件的内存管理
要点:自动的事件内存管理只有在引擎为Stream模式下才有效。
引擎在Steam模式下的好处之一是引擎能够根据时间约束检测出不再匹配任何规则的事件,然后引擎可以没有负作用的安全地回收事件并释放其相关的资源。
引擎有2种基本方式计算一个事件有效期:
(1)显式,使用过期(expiration)策略
(2)隐式,分析事件的时间约束
1.显式的过期偏移量(有效期)
//explicitly defining an expiration offset of 30 minutes for StockTick events
declare StockTick
@expires( 30m )
end
当StockTick事件持续30分钟后,如果没有规则需要该事件,引擎将自动的回收该事件。
2.推知的过期偏移量
//example rule with temporal constraints
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隐式的过期偏移量是0。
引擎会分析整个Knowledgebase,找出每个事件类型的过期偏移量。当隐式过期偏移量和显式过期偏移量冲突时,引擎会选择2个值中最大的1个
作者:forge(作者的Blog:http://forge.iteye.com)
原文:http://forge.iteye.com/blog/571047
Drools Fusion Features
Fusion 是业务逻辑集成平台(Business Logic Integration Platform)一部分,是一个CEP/ESP引擎。
事件,是指在应用程序域中有意义状态改变的记录
一个复杂事件是简单事件的集合。CEP(Complex Event Processing) 处理复杂事件-从事件云中检测并选择事件,根据选择的事件和他们建立的关系发现他们的关系推理出新的数据。
比如,一系列的巨额提款激发可疑交易事件的发生。一个复杂事件的发生是由一系列简单事件的引导形成的。
ESP(Event Stream Processing) 是更实时(real-time)的大量事件处理。例如,根据时间计算实时平均交易量。
一、事件(Events).
从drools视图来看,事件就是一个特殊的事实(fact)。我们说,所有的事件都是事实,但是所有的事实并不都是事件。
1.事件和事实不同特征:
(1)通常不可变:事件是在应用程序域中状态改变的记录,记录已经发生的事情,可变的事件是没有意义的。
应用程序允许给没有赋值的事件属性赋值,但是已经赋值的属性是不应该被改变的。
(2)强时间约束:规则涉及的事件通常需要多个事件的相互关系,尤其某个事件相对其他事件发生的时间点的时间关系。
(3)可管理的生命周期:由于上两个事件特征的原因,事件通常在一个有限的时间窗(Time Window)匹配事件和事实,
这就让引擎管理事件的生命周期成为可能。一个事件插入working memory,引擎有能力发现一个不在匹配
其他事实的事件,然后自动回收它,释放相关的资源。
(4)滑动窗( sliding windows)的使用:由于所有的事件都有关联的时间戳,可以定义并使用滑动窗,让事件在特定的一段时间有效。
2.事件声明:
import some.package.StockTick
declare StockTick
@role( event )
end
将一个已有的StockTick(类)事实生命为事件类型
@role 元数据接受2个值:
fact:默认值,声明为常规的事实
event:声明为事件
由于Drools支持在DRL中声明一个事实,可以去掉StockTick(类),还可以这样:
declare StockTick
@role( event )
datetime : java.util.Date
symbol : String
price : double
end
3.事件元数据
所有的事件都有一个事件元数据集合。当事件插入working memory,事件元数据会自动赋予默认值,我们可以用元数据标签改变他们的默认值。
假设我们有这样的一个实体类:
/**
* A class that represents a voice call in
* a Telecom domain model
*/
public class VoiceCall {
private String originNumber;
private String destinationNumber;
private Date callDateTime;
private long callDuration; // in milliseconds
// constructors, getters and setters
}
(1) @timestamp
默认的,当事件插入working memory,会读取(会话时钟)Session Clock的timestamp赋值给事件的startTimestamp。
@timestamp( <attributeName> ) //以毫秒计算
表示把事件的attributeName属性值赋给事件元数据startTimestamp
declare VoiceCall
@role( event )
@timestamp( callDateTime )
end
(2) @duration
Drools支持时间点(point-in-time)事件和时间间隔(interval-based)事件,一个时间点事件可以理解为时间间隔为0。默认,所有的事件的持续时间(duration)为0.我们可以通过@duration标签来修改事件的duration属性值。
@duration( <attributeName> )// 以毫秒计算。
表示把事件的attributeName属性值赋给事件元数据duration
declare VoiceCall
@role( event )
@timestamp( callDateTime )
end
(3)@expires
这个标签在引擎为Stream模式下才有效。默认的,当一个事件不在匹配(match)事实,激活规则(有活化的规则),事件自动失效(回收).
@expires( <timeOffset> )
显示的定义事件的失效时间。
timeOffset可以是下列形式:
[#d][#h][#m][#s][#[ms]]
[]表示是可选参数
declare VoiceCall
@role( event )
@timestamp( callDateTime )
@duration( callDuration )
@expires( 1h35m )
end
VoiceCall事件在1小时35分钟后过期。
二、会话时钟(Session Clock)
Drools 5提供2种时钟。默认的是基于系统时间的即时时钟(real time clock)。另一个是虚拟(pseudo clock)时钟,可以手动控制。
1. 即时时钟(Real Time Clock)
虽然Drools默认使用即时时钟,我们仍然可以显示的配置:
KnowledgeSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
config.setOption( ClockTypeOption.get("realtime") );
2. 配置虚拟时钟(Pseudo Clock)
KnowledgeSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
config.setOption( ClockTypeOption.get("pseudo") );
怎样控制虚拟时钟的例子:
KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
conf.setOption( ClockTypeOption.get( "pseudo" ) );
StatefulKnowledgeSession session = kbase.newStatefulKnowledgeSession( conf, null );
SessionPseudoClock clock = session.getSessionClock();
// then, while inserting facts, advance the clock as necessary:
FactHandle handle1 = session.insert( tick1 );
clock.advanceTime( 10, TimeUnit.SECONDS );
FactHandle handle2 = session.insert( tick2 );
clock.advanceTime( 30, TimeUnit.SECONDS );
FactHandle handle3 = session.insert( tick3 );
新的SessionPseudoClock时间值为0毫秒,new Date(0) 为GMT 1970 年 1 月 1 日 00:00:00 ,
clock.advanceTime( 10, TimeUnit.SECONDS ); SessionPseudoClock时间值+10秒。
三、Entry Points
一个entry point是事实(facts)或者事件(events)进入引擎的入口点。
1.声明Entry Points
rule "authorize withdraw"
when
WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream"
CheckingAccount( accountId == $ai, balance > $am )
then
// authorize withdraw
end
只有账户的余额大于提款请求的金额,规则才被激活
2.使用Entry Points
// create your rulebase and your session as usual
StatefulKnowledgeSession session = ...
// get a reference to the entry point
WorkingMemoryEntryPoint atmStream = session.getWorkingMemoryEntryPoint( "ATM Stream" );
// and start inserting your facts into the entry point
atmStream.insert( aWithdrawRequest );
通常情况下,我们不需要硬编码把事实插入到WorkingMemory中,通过Drools pipeline,
将从JMS或者 其他非java对象的数据源 中获得信息(对象)插入到WorkingMemory。
默认的entry point是"DEFAULT",是事实进入WorkingMemory的入口点。
session.insert(fact)实际上是session.getWorkingMemoryEntryPoint("DEFAULT").insert(fact)。
四、时间推理(Temporal Reasoning)
Drools 实现了由Allen定义的13种时间操作运算
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
Temporal Operators ┃ Illustration ┃ Interpretation
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x before y ┃ ___y___ ┃ X发生在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___y___ ┃
x after y ┃ ___x___ ┃ X发生在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x meets y ┃ ___y___ ┃ x结束时y开始
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___y___ ┃
x metby y ┃ ___x___ ┃ y结束时x开始
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x overlaps y ┃ ______y______ ┃ x开始在y之前,结束在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x overlappedby y ┃ ______y______ ┃ x开始在y之后,结束在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x starts y ┃ ______y______ ┃ x和y同时开始,结束在y之前
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x startedby y ┃ ___y___ ┃ x和y同时开始,结束在y之后
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x during y ┃ ______y______ ┃ x发生在y期间
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x includes y ┃ ___y___ ┃ y发生在x期间
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ___x___ ┃
x finishes y ┃ ______y______ ┃X开始在y之后,同y一起结束
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x finishedby y ┃ ___y___ ┃X开始在y之前,同y一起结束
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
┃ ______x______ ┃
x coincides y ┃ y ┃ X和y同时发生
━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━
1.before
$eventA : EventA( this before[ 3m30s, 4m ] $eventB )
也就是:
3m30s <= $eventB.startTimestamp - $eventA.endTimeStamp <= 4m
before操作符的时间间隔距离是可选的:
如果2个值都被定义了,间隔开始于第1个值,结束于第2个值。
如果只有1个值被定义,间隔开始于该值,结束于正无穷大。
如果没有值定义,间隔开始于1毫秒,结束于正无穷大。
时间间隔距离也可以是负数的,例:
$eventA : EventA( this before[ -3m30s, -2m ] $eventB )
注意:当时间间隔第1个值大于第2个值时,引擎会自动的调换他们。
2.After
$eventA : EventA( this after[ 3m30s, 4m ] $eventB )
也就是:
3m30s <= $eventA.startTimestamp - $eventB.endTimeStamp <= 4m
after操作符的时间间隔距离是可选的:
如果2个值都被定义了,间隔开始于第1个值,结束于第2个值。
如果只有1个值被定义,间隔开始于该值,结束于正无穷大。
如果没有值定义,间隔开始于1毫秒,结束于正无穷大。
时间间隔距离也可以是负数的,例:
$eventA : EventA( this after[ -3m30s, -2m ] $eventB )
注意:当时间间隔第1个值大于第2个值时,引擎会自动的调换他们。
3.meets
$eventA : EventA( this meets $eventB )
也就是:
abs( $eventB.startTimestamp - $eventA.endTimestamp ) == 0
meets操作符有1个可选参数
$eventA : EventA( this meets[ 5s ] $eventB )
也就是:
abs( $eventB.startTimestamp - $eventA.endTimestamp) <= 5s
注意:时间间隔不能为负
4.metby
$eventA : EventA( this metby $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.endTimestamp ) == 0
metby操作符有1个可选参数
$eventA : EventA( this metby[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.endTimestamp) <= 5s
注意:时间间隔不能为负
5.overlaps
$eventA : EventA( this overlaps $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp
overlaps操作符有1个/2个可选参数
$eventA : EventA( this overlaps[ 5s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp &&
0 <= $eventA.endTimestamp - $eventB.startTimestamp <= 5s
$eventA : EventA( this overlaps[ 5s, 10s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp < $eventA.endTimestamp < $eventB.endTimestamp &&
5s <= $eventA.endTimestamp - $eventB.startTimestamp <= 10s
6.overlappedby
eventA : EventA( this overlappedby $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp
overlappedby操作符有1个/2个可选参数
$eventA : EventA( this overlappedby[ 5s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp &&
0 <= $eventB.endTimestamp - $eventA.startTimestamp <= 5s
$eventA : EventA( this overlappedby[ 5s, 10s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp < $eventB.endTimestamp < $eventA.endTimestamp &&
5s <= $eventB.endTimestamp - $eventA.startTimestamp <= 10s
7.starts
$eventA : EventA( this starts $eventB )
也就是:
$eventA.startTimestamp == $eventB.startTimestamp &&
$eventA.endTimestamp < $eventB.endTimestamp
starts操作符有1个可选参数
$eventA : EventA( this starts[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 5s &&
$eventA.endTimestamp < $eventB.endTimestamp
注意:时间间隔不能为负
8.startedby
$eventA : EventA( this startedby $eventB )
也就是:
$eventA.startTimestamp == $eventB.startTimestamp &&
$eventA.endTimestamp > $eventB.endTimestamp
starts操作符有1个可选参数
$eventA : EventA( this starts[ 5s ] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 5s &&
$eventA.endTimestamp > $eventB.endTimestamp
注意:时间间隔不能为负
9.during
$eventA : EventA( this during $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp <= $eventA.endTimestamp < $eventB.endTimestamp
during操作符有1个/2个/4个可选参数
$eventA : EventA( this during[ 5s ] $eventB )
也就是:
0 < $eventA.startTimestamp - $eventB.startTimestamp <= 5s &&
0 < $eventB.endTimestamp - $eventA.endTimestamp <= 5s
$eventA : EventA( this during[ 5s, 10s ] $eventB )
也就是:
5s <= $eventA.startTimestamp - $eventB.startTimestamp <= 10s &&
5s <= $eventB.endTimestamp - $eventA.endTimestamp <= 10s
$eventA : EventA( this during[ 2s, 6s, 4s, 10s ] $eventB )
也就是:
2s <= $eventA.startTimestamp - $eventB.startTimestamp <= 6s &&
4s <= $eventB.endTimestamp - $eventA.endTimestamp <= 10s
10.includes
$eventA : EventA( this includes $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp <= $eventB.endTimestamp < $eventA.endTimestamp
includes操作符有1个/2个/4个可选参数
$eventA : EventA( this includes[ 5s ] $eventB )
也就是:
0 < $eventB.startTimestamp - $eventA.startTimestamp <= 5s &&
0 < $eventA.endTimestamp - $eventB.endTimestamp <= 5s
$eventA : EventA( this includes[ 5s, 10s ] $eventB )
也就是:
5s <= $eventB.startTimestamp - $eventA.startTimestamp <= 10s &&
5s <= $eventA.endTimestamp - $eventB.endTimestamp <= 10s
$eventA : EventA( this includes[ 2s, 6s, 4s, 10s ] $eventB )
也就是:
2s <= $eventB.startTimestamp - $eventA.startTimestamp <= 6s &&
4s <= $eventA.endTimestamp - $eventB.endTimestamp <= 10s
11.finishes
$eventA : EventA( this finishes $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishes操作符有1个可选参数
$eventA : EventA( this finishes[ 5s ] $eventB )
也就是:
$eventB.startTimestamp < $eventA.startTimestamp &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 5s
注意:时间间隔不能为负
12.finishedby
$eventA : EventA( this finishedby $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishedby操作符有1个可选参数
$eventA : EventA( this finishedby[ 5s ] $eventB )
也就是:
$eventA.startTimestamp < $eventB.startTimestamp &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 5s
注意:时间间隔不能为负
13.coincides
$eventA : EventA( this coincides $eventB )
也就是:
$eventA.startTimestamp ==$eventB.startTimestamp &&
$eventA.endTimestamp == $eventB.endTimestamp
finishedby操作符有1个/2个可选参数
$eventA : EventA( this coincides[15s] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 15s &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 15s
$eventA : EventA( this coincides[15s, 10s] $eventB )
也就是:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 15s &&
abs( $eventA.endTimestamp - $eventB.endTimestamp ) <= 10s
注意:时间间隔不能为负
五、事件处理模式(Event Processing Modes)
Drools支持2种事件处理模式:云模式(Cloud Mode)和流模式(Stream Mode)
1.云模式(Cloud Mode)
云(Cloud)处理模式是默认的处理方式。
在云模式下,不会区分事实和事件,都看成是事实。
(1)没有时间的概念。尽管事件在插入引擎被赋予了时间戳,也不能判断该事件“多大了”,因为没有“现在”的概念。滑动窗(sliding windows)基于“现在”的概念,所以在云模式下无法使用。
(2)无序的事件云。由于事件无序,没有自动的生命周期管理,需要像正常的事实一样显示的删除事件。
云模式虽然是默认的执行模式,我们也可以配置它:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.CLOUD );
等同系统属性配置:
drools.eventProcessingMode = cloud
2.流模式(Stream Mode)
当处理事件流的时候需要选择流处理模式。
在流模式下:
(1) 插入到引擎里的事件必须是时间顺序的。
(2) 引擎强制性的和使用的会话时钟session clock同步。
配置流模式:
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
等同配置系统属性:
drools.eventProcessingMode = stream
使用流(STREAM)模式,引擎有时间流和"现在"的概念(通过读取Session Clock的时间戳),
提供了以下3种支持:
(1) 滑动窗的支持
(2) 自动的时间生命周期管理
(3) 使用消极模式(Negative Patterns)自动的规则延迟
3.会话时钟(Session Clock)在流模式(Stream mode)中的作用
在云模式下,会话时钟只有一个作用,就是给插入到working momery 的事件赋予时间戳的值(如果规则没有定义时间戳属性)
在流模式下,会话时钟负责维护当前时间戳,基于当前的时间戳,引擎根据事件的年龄计算所有时间运算,从多种源同步流,安排未来任务等等。
4.流模式(in Stream Mode)中的消极模式(Negative Patterns)
消极模式在流模式和云模式意义是不同的。
在云模式下,所有的事实和事件都是预先知道的,消极模式立即计算执行
//a rule that activates immediately upon matching
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( ) )
then
// sound the alarm
end
在流模式下,带有时间约束的消极模式可以要求引擎等待一段时间后激活规则。
//a rule that automatically delays activation due to temporal constraints
rule "Sound the alarm"
when
$f : FireDetected( )
not( SprinklerActivated( this after[0s,10s] $f ) )
then
// sound the alarm
end
六. 滑动窗(Sliding Windows)
滑动窗是一种选择事件范围的方式。
滑动窗有2种实现方式:滑动时间窗(Sliding Time Windows),滑动长度窗(Sliding Length Windows)。
注意:滑动窗只有在引擎为(STREAM)流模式下才是可用的
1.滑动时间窗(Sliding Time Windows)
滑动时间窗(允许我们的规则)仅仅匹配发生在最近X时间单元的事件。
例如,如果只关心最近2分钟的股票行情,模式(pattern)可以这样写:
StockTick() over window:time( 2m )
更复杂的例子:
如果最近10分钟从传感器读来的温度高于最大的临界值,发出警报。
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
2.滑动长度窗(Sliding Length Windows)
滑动长度窗(允许我们的规则)仅仅匹配发生在最近X个的事件。
例如,如果只关心最近10个IBM股票的行情,模式(pattern)可以这样写:
StockTick( company == "IBM" ) over window:length( 10 )
更复杂的例子:
如果最近100次从传感器读来的温度高于最大的临界值,发出警报。
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
七.Knowledgebase分割(Partitioning)
(注意:这是个实验性的特征,将来可能会发生变化)
传统的Rete算法通常一个线程在执行,也是Drools默认的。但是,该算法本身是可平行(化)的。Drools执行的ReteOO算法通过Knowledgebase分割支持粗粒度的平行执行。
当这个选项可用,Knowledgebase分割成若干个独立的区域,然后用线程池通过这些区域传播事实。该实现保证最多有一个线程在给定的一个区域执行。
要点:这个特征只在LHS平行执行可用,不会改变规则激活行为。
1.平行执行在什么时候有益:
(1)多处理器
(2)knowledge session处理大量的事实
(3)规则的LHS计算量大
(4)knowledge base包含数百或者更多的规则
如果以上条件都符合,这个特征可能会提高knowledgebase计算总性能。
2.配置Knowledgebase分割
//enabling multithread evaluation (partitioning)
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MultithreadEvaluationOption.YES );
等同系统属性配置:
drools.multithreadEvaluation = <true|false>
3.多线程管理
配置线程池:
//setting the maximum number of threads for rule evaluation to 5
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( MaxThreadsOption.get(5) );
等同系统属性配置:
drools.maxThreads = <-1|1..n>
配置默认值是3,负数值表示在Knowledgebase中有多少分区,引擎就会产生多少线程,使用负数值是很危险的。
八.事件的内存管理
要点:自动的事件内存管理只有在引擎为Stream模式下才有效。
引擎在Steam模式下的好处之一是引擎能够根据时间约束检测出不再匹配任何规则的事件,然后引擎可以没有负作用的安全地回收事件并释放其相关的资源。
引擎有2种基本方式计算一个事件有效期:
(1)显式,使用过期(expiration)策略
(2)隐式,分析事件的时间约束
1.显式的过期偏移量(有效期)
//explicitly defining an expiration offset of 30 minutes for StockTick events
declare StockTick
@expires( 30m )
end
当StockTick事件持续30分钟后,如果没有规则需要该事件,引擎将自动的回收该事件。
2.推知的过期偏移量
//example rule with temporal constraints
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隐式的过期偏移量是0。
引擎会分析整个Knowledgebase,找出每个事件类型的过期偏移量。当隐式过期偏移量和显式过期偏移量冲突时,引擎会选择2个值中最大的1个
相关推荐
基于springboot教育资源共享平台源码数据库文档.zip
linux开发篇,配套视频:https://www.bilibili.com/list/474327672?sid=4493702&spm_id_from=333.999.0.0&desc=1
ReadEra 这个阅读应用能够打开下列任何格式的文档: EPUB, PDF, DOC, RTF, TXT, DJVU, FB2, MOBI, 和 CHM. 基本上来说,你可以用它阅读你的设备内存中的任何书籍或者文本文档。 这个应用与划分成章节的文档兼。,有一个书签功能,可以在你阅读的时候,自动保存你的进度。另外,它让你更改页面模式,从几种不同的主题中进行挑选(夜间,白天,棕黑色调,还有控制台)。
软件环境:KEIL4 硬件环境:STM32单片机+舵机 控制原理:通过控制输出信号的占空比调节舵机旋转的角度
基于springboot仓库管理系统源码数据库文档.zip
酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。 酒店管理系统源码C++实现的毕业设计项目源码.zip,酒店管理系统源码C++实现的毕业设计项目源码.zip个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。酒店管理系统源码C++实现的毕业设计项目源码.zip酒店管理系统源码C++实现的毕业设计项目源码.zip酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕
58商铺全新UI试客试用平台网站源码
springboot vue3前后端分离 基于SpringBoot+Vue的轻量级定时任务管理系统.zip
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
4D毫米波雷达点云数据处理方法研究.caj
S M 2 2 5 8 X T 量产工具供大家下载使用
基于springboot的文物管理系统源码数据库文档.zip
基于springboot的电影院售票管理系统源码数据库文档.zip
基于Java web 实现的仓库管理系统源码,适用于初学者了解Java web的开发过程以及仓库管理系统的实现。
美容美发项目,使用django框架,前后端一体化项目
在线票务:2023年中国在线票务行业市场规模约为24.99亿元,挖掘市场蓝海新机遇 在数字浪潮的席卷下,传统的票务销售模式正经历着前所未有的变革。纸质门票逐渐淡出人们的视野,取而代之的是便捷、高效的数字和移动票务。这一转变不仅为消费者带来了前所未有的购票体验,更为在线票务平台开辟了广阔的发展空间和市场机遇。随着国民经济的持续增长和文体娱乐行业的蓬勃发展,中国在线票务行业正站在时代的风口浪尖,等待着每一位有志之士的加入。那么,这片蓝海市场究竟蕴藏着怎样的潜力?又该如何把握机遇,实现突破?让我们一同探索。 市场概况: 近年来,中国在线票务行业市场规模持续扩大,展现出强劲的增长势头。据QYResearch数据显示,2023年中国在线票务行业市场规模约为24.99亿元,尽管受到宏观经济的影响,市场规模增速放缓,但整体趋势依然向好。这一增长主要得益于国民人均收入的不断提高、电影及演出行业的快速发展以及政府政策的支持。例如,2023年财政部、国家电影局发布的《关于阶段性免征国家电影事业发展专项资金政策的公告》,为电影行业注入了强劲动力,进而推动了在线票务市场规模的扩大。 技术创新与趋势: 技术进步
基于SpringBoot的养老院管理系统源码数据库文档.zip
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
内容概要:本文档是一份详细的Go语言教程,从基础概念介绍到高级主题均有覆盖。主要内容包括Go语言的基础语法、数据类型、控制结构、函数、结构体、接口和并发编程等方面。通过具体示例介绍了如何使用Go语言进行开发。 适合人群:初学者和有一定经验的程序员都可以从这篇教程中受益,特别是那些想要快速掌握Go语言并应用于实际项目的开发者。 使用场景及目标:适用于初学者系统学习Go语言的基础知识和常用功能;也可以作为已有开发经验者的参考资料,帮助他们解决具体的编程问题,提高开发效率。 其他说明:本教程不仅包含了Go语言的基本知识点,还重点讲解了其独特的并发编程模型。读者在学习过程中应该注重理论与实践相结合,通过实际编写代码来加深理解和记忆。
基于springboot计算机基础网上考试系统源码数据库文档.zip