SODBASE CEP学习(五):流式计算中的类SQL语言EPL

开发者社区活动,SODBASE产品的用户现在可以领礼品 

本文中类SQL语句建模、单元测试建议使用SODBASE Studio,参考示例见视频教程

SODBASE CEP中,类SQL语言EPL(事件处理语言)也叫做SODSQL。其基本写法为

CREATE QUERY 查询名称
SELECT 查询字段
FROM  流
PATTERN 复杂事件模式
WHERE 条件
WITHIN 时间窗口大小

比传统SQL就多了两个东西PATTERNWITHIN

PATTERN:复杂事件表达式,由事件类名和操作符构成,操作符含义参见下文。

CEP理论通常规定事件有两个时间戳,即开始时间和结束时间。SODBASE CEP对应数据的_start_time_和_end_time_属性。CEP理论对事件也作了区分:基本事件和复杂事件。基本事件往往代表着原始数据,开始时间等于结束时间。可以这样理解,如果说流是一张无限长的关系表,那基本事件就是关系表中一条条记录,并带了时间戳(时间戳可以是系统给的,也可以是自定义的)。复杂事件则是由基本事件通过各种时间关系、逻辑关系所组成的事件。

WITHIN:是指输出的复杂事件e的结束时间减去e的开始时间需<=窗口大小。只有满足这个条件的复杂事件e才会被输出。

FROM:为事件类指定数据流来源。例如: FROM T1:stockstream,T2:pos 其中T1是股价流stockstream的别名,T2是pos交易流的别名。而stockstream、pos流可以看做两张无限长的关系表。表包含的字段是在输入适配器定义的。 从2.0.23(sp1)版本开始,别名和流名称可以空格分隔,如FROM stockstream T1,streamname alias等价于FROM T1:stockstream,alias:streamname。

SELECT:被查询字段或聚合函数、自定义函数。如果设置为*,则获取整个POJO明细。另外,输出适配器的isOutputAsSelection设置为false,也会让SELECT不起作用,从而获取整个POJO明细。这时,通常需自定义输出适配器来选取或计算数据。

简单过滤(Filter)查询PATTERN直接写事件类名,不需要复杂事件操作符。如电压监测中,选取数据作实时图形显示。

CREATE QUERY VD0001 
SELECT T1.lineid AS fusionwidgetsid,T1.lineid AS lineid,T1.voltagevalue AS voltagevalue 
FROM T1:voltagestream 
PATTERN T1
WHERE T1.lineid='110kvline' 
WITHIN 0

WITHIN 设置为0,是因为Filter通常不需要窗口,每条基本事件数据按条件过滤。这条语句连接到图形显示输出适配器就可以实时显示线路'110kvline'的电压图形了。

FROM T1:voltagestream 的T1为事件类名,voltagestream是数据流名。类似关系数据库,也可以这样理解,voltagestream流是一张无限长的关系表,T1则是voltagestream的别名。下文中读者会发现,同一个流有多个别名,多个别名在一个流上来构建复杂模式。

1. 复杂事件基本操作符

1.1与模式(都发生)

Conjunction(A&B):表示事件A、事件B都发生,但不规定A、B发生的顺序。

例:IT系统运维监控中,“服务调用开始”触发一个事件,在“调用结束”触发一个事件,如果此调用的处理时间没超时(1000ms)的话,就输出服务处理时间

CREATE QUERY callnottimeout
SELECT T2._start_time_-T1._start_time_ AS responsetime, T1.callerid AS functionname ,'false' AS timeout
FROM T1:callstream,T2:callstream
PATTERN T1&T2
WHERE T1.callerid=T2.callerid  AND T1.eventtype ='start' AND T2.eventtype ='end'
WITHIN 1000

1.2顺序发生

Sequence(A;B): 表示事件A在事件B后发生,即事件A的结束时间小于事件B的开始时间。

例:当RFID Reader 读到标签后,在1s内该标签还在则认为是连续读到该标签,如果2s后该标签还存在,则触发进入通道事件

CREATE QUERY readenterchannel
SELECT 'enterchannel' AS type,T3.id AS T3_id,T3.num AS num
FROM T1:readerevent,T2:readerevent,T3:readerevent
PATTERN T1;T2;T3 
WHERE T1.id=T2.id AND T1.id=T3.id AND T2._end_time_-T1._end_time_<=1000 AND T3._end_time_-T2._end_time_<2000 AND T3._end_time_-T1._end_time_>1000  
WITHIN 3000


1.3 非模式

Negation(!A):表示事件A不发生。非模式必须在两个基本事件之间使用。如“A;!B;C”表示A、C顺序发生,但其间不能有B发生。用在超时监测,事务审计等场景中比较多。

例:当RFID Reader 读到标签后,2秒内没有再读到标签,则认为标签离开了通道

CREATE QUERY readoutofchannel2
SELECT T1.id AS T1_id,T1.num AS T1_num,'outofchannel' AS type
FROM T1:readerevent,T2:readerevent,T3:delay2sectimer
PATTERN T1;!T2;T3 
WHERE T1.id=T3.id AND T2.id=T3.id AND T3._end_time_-T1._end_time_=2000 
WITHIN 2000

为了保证结果正确性,SODBASE CEP引擎规定条件中"非事件"T2不能与它之前的事件T1做关联,而要与T3关联,即不能有类似T1.id=T2.id的条件。

例:IT系统运维监控中,“服务调用开始”触发一个事件,在“调用结束”触发一个事件,如果超时(1000ms)的话,就输出超时事件

CREATE QUERY calltimeoutnotification 
SELECT '-1' AS responsetime, T1.callerid AS functionname,'true' AS timeout 
FROM T1:callstream,T2:callstream,T3:calltimeoutevent 
PATTERN T1;!T2;T3  
WHERE T3._end_time_-T1._end_time_=1000 AND T2.callerid=T3.callerid AND T1.eventtype='start' 
WITHIN 1000 

1.4或模式

Disjunction(A|B):表示事件A发生或事件B发生或两者都发生。

例:变电站监测中,查询220KV I段PT电压或其它段电压>112的事件

CREATE QUERY VD0000_1 
SELECT * 
FROM T1:VD0000_1.模拟电压,T2:VD0000_1.模拟电压 
PATTERN T1|T2 
WHERE T1.lineid='220KV I段PT电压' 
AND T2.voltagevalue>112 
WITHIN 500 


1.5克林包(Kleen Closure)

Kleen Closure(A^+/A^num): A^+表示事件A发生1次或多次;A^num中num为数字,表示事件A发生num次。kleen closure必须在两个基本事件之间使用。例如: A;B^+;C表示A发生后,B发生多次,然后C发生

还有一个A^*,表示A发生0次或多次。但是A发生0次时取A的字段值是空值,使用时要注意,测试保证能取到结果。否则请使用(A;C)|(A;B^+;C)来代替A;B^*;C。


1.5.1 普通窗口查询

例:金融风控中,查询1小时内的交易大于10000且交易次数大于2笔的卡号

CREATE QUERY pos
SELECT T1.acctnum AS acctum,T1.value AS T1_value,tostring(T2.value) AS T2.value,T3.value AS T3_value
FROM T1:pos,T2:pos,T3:pos
PATTERN T1;T2^+;T3
WHERE T1. acctnum =T2. acctnum
AND T3. acctnum =T1. acctnum
AND T1.value+T3.value+sum(T2.value)>10000
WITHIN 3600000

1.5.2定时统计

例:查询股价的10秒钟K线数据

CREATE QUERY vwap1 
SELECT min(T2.price) AS LOW, max(T2.price) AS HIGH 
FROM T1:timer,T2:stock,T3:timer 
PATTERN T1;T2^+;T3  
WHERE T1._start_time_=T3._start_time_-10000  
WITHIN 10000

average是内置聚合函数,还有sum、tostring、max、min、first、count、countdistinct、tostringdistinct等。timer流是用定时触发输入适配器生成的,周期为10000ms。相当于每个10秒窗口被T1和T3这两个定时触发时间点给括起来了。

例:整点开始统计12:00:00~12:00:10,12:00:10~12:00:20等每10秒的股票数据

很多情况下,我们还想将每个窗口的起始时间设为整点开始,按一定的滑动窗口统计数据。

一种方法是用带起始时间的定时触发器,设置定时器开始时间,如2014-01-01 12:00:00,周期(ms)为10000。在sodbase cep 2.0.20(sp1)之后版本中如果开始时间在当前时间之前,会按照周期调整到当前时间之后的触发点。

另外,前例语句中T1和T3将T2括起来是开区间窗口,即T1._end_time_<T2._start_time<=T2._end_time<T3._start_time_。如果要精确的包含timer时间点上的数据,可以再用一个适配器:延时适配器。将timer1通过延时适配器延时1ms输出为timer2流。也就是timer1和timer2起始时间相差一个单位时间。最终的EPL语句为

CREATE QUERY stockquery 
SELECT min(T2.price) AS LOW, max(T2.price) AS HIGH 
FROM T1:timer1,T2:stock,T3:timer2 
PATTERN T1;T2^+;T3  
WHERE T1._start_time_=T3._start_time_-10001  
WITHIN 10001


1.5.3 窗口内数据聚类(GROUP BY)

针对kleen closure进行数据分组。GROUP BY需紧接着PATTERN写。

例:统计每一只股票的10s的统计值,2s输出一次

timer为定时输入适配器,周期为2s

CREATE QUERY demo 
SELECT average(T2.price) AS price_avg, T2.name AS name  
FROM T1:timer,T2:stock,T3:timer 
PATTERN T1;T2^+;T3  
GROUP BY T2.name
WHERE T1._start_time_=T3._start_time_-10000  
WITHIN 10000

例:网络流量分析中,统计10s内从源地址到目的地址的package数量

SELECT T2.src AS src,.destination AS destination, count(T2.src) AS count 
FROM T1:timer1,T2:netstream,T3:timer2 
PATTERN T1;T2^+;T3  
GROUP BY T2.src,T2.destination  
WHERE T3._start_time_-T1._start_time_=10001  
WITHIN 10001 
netstream是流量包信息,包含源地址src、目的地址destination、通信协议、包大小等数据。timer1、timer2是相差1ms的定时器,周期都是10s

1.5.4 事件触发的窗口开启

例:  每当股价超过阈值(>50.0)后,开启10秒窗口, 统计10秒内股价超过阈值的事件,这10秒内的超过阈值事件, 就不再开启新的10秒窗口

CREATE QUERY delayoutput2 
SELECT * 
FROM T1:delayoutput.stock,T2:delayoutput.stock,T3:delayoutput2.intervaltimer 
PATTERN T1;T2^*;T3  
WHERE T1._start_time_=T3._start_time_-10000  AND T1.price>50 AND T2.price>50  
WITHIN 10000 
BATCHMODE
流delayoutput2.intervaltimer中是每个股价数据延时10s生成的事件,与delayoutput.stock中的事件一一对应形成10秒窗口,参见下文延时输出适配器

BATCHMODE关键词表示检测到结果后,滑动窗口的开始时间移到之前结果的结束时间后。即下一个输出结果的T1._start_time_>=前一个结果的T3._end_time_。

类似的例子还有

例:电信设备监测中发现故障后,开启窗口记录设备详细信息。

例:车辆行驶记录发现异常后,开启窗口监测车辆数据做记录。

1.5.5 查询窗口内某字段最大值对应的事件信息

:在IT设备监测中,监测CPU使用率最大的机器的ID,滑动窗口为10秒

 CREATE QUERY maxCPUMachine 
 SELECT JAVASTATIC:f.Top:getByIndex(tostring(T2.machineId),JAVASTATIC:f.Top:indexOfMax(tostring(T2.cpu_rate))) AS max_machineId,
        max(T2.cpu_rate) AS max_cpu_rate 
   FROM T1:timer1,T2:cpustream,T3:timer1 
PATTERN T1;T2^+;T3  
 WITHIN 10000 
注:如果是一张静态的传统关系型数据库表,这类查询通常需要先查出最大值,然后与原表进行Join得到CPU使用率最大的对应设备。在这里,我们直接使用自定义函数去取CPU使用率最大的对应设备ID了。在流式数据中,做Topk查询也可以采用类似思路,或者自定义输出适配器来处理复杂的聚合运算。

1.5.6 标记窗口内发生某事件

例:5秒一个窗口,窗口批次滑动,查窗口内某设备的CPU使用率>0.8

   SELECT JAVASTATIC:f.Math:i_divide(T1._start_time_,'5000') AS timeinterval,T2.machine_id 
    FROM  T1:timer1,T2:nextstream,T3:timer2 
 PATTERN  T1;T2^+;T3  
GROUP BY  T2.machine_id
   WHERE  T3._start_time_-T1._start_time_=5001  AND T2.cpu_rate>0.8
  WITHIN  5001 

注:i_divide为自定义函数做整数除法。

1.5.7 非严格顺序的Kleen Closure窗口(EXTENDED关键字)

前文讲到的A;B^*;C默认B要在A之后发生,C要在B之后发生。

使用EXTENDED关键字后,A._end_time_=B._start_time_,B._end_time_=C._start_time_复杂事件也会被输出。使用此关键字,系统只保证相同时间戳的数据全部输入后,才能够监测到所有情况。

1.6 使用括号

可以使用括号构造更为复杂的模式,在实际应用中有时也会使用到。

例如:(T1;T2^2;T3)|(T4;T5)

当然,为了使模型拓扑更清晰和单元测试更方便,建议是用级联方式(见级联输入输出适配器)构建CEP模型,而不是写非常复杂的PATTERN表达式。上例中,可以用一个单元模型(T1;T2^2;T3)输出级联到流stream1,另一个单元模型T4;T5输出级联到stream2。再用一个单元模型T6|T7 FROM T6:stream1,T7:stream2得到最终结果。

2.自定义函数和内置函数

2.1  自定义函数

在EPL的SELECT和WHERE语句中都可以方便地调用用户自己写的Java函数

例:IT系统运维监控中,查询服务调用和子服务调用之间的时间差

CREATE QUERY calltime
SELECT A.callerid,B.callerid,JAVA:com.example.CallAnalysis:minus(A.time,B.time) AS timecost 
FROM A:callstream,B:callstream 
PATTERN A;B  
WHERE JAVA:com.example.CallAnalysis:parentOf(A.callerid,B.callerid)   
WITHIN 6000 

写Java函数时,参数需要是String类型,返回需要是String,Double,Integer,Float。

在最新的SODBASE CEP引擎中以JAVA开头调用对象方法,以JAVASTATIC开头调用类静态方法。


例:IT系统监控中,CPU利用率超过80%,报警一次。接下来的2min中,如果CPU利用率还超80%,也不报警。

CREATE QUERY reduceAlarmNum
SELECT 'CPU_HIGH' AS event.type, JAVA:package.class:setLastAlarmTime('CPU_HIGH',event._start_time_) AS setalarmstate 
FROM event:cpuusagestream
PATTERN event
WHERE event.cpu_usage>0.8 AND event._start_time_-JAVA:package.class:getLastAlarmTime('CPU_HIGH')>12000
WITHIN 0

这里的JAVA:package.class:getLastAlarmTime('CPU_HIGH')和JAVA:package.class:setLastAlarmTime('CPU_HIGH',event._start_time_) 是自定义Java函数,作用是在全局变量或缓存、存储系统中记录上次同类报警的时间。

2.2 内置函数

内置函数是为了方便使用,引擎自带一些内置函数,内置函数的字母均为小写。如前文提到的克林包上的average、sum、tostring、max、min、first、count、countdistinct、tostringdistinct函数。新版本中还支持下面几个常用函数。

字符串连接函数:concat

布尔运算函数:and、or、xor

为了避免函数嵌套的类型匹配不正确,建议尽量使用自定义函数。


3.常用输入输出适配器

要在实际项目中用好EPL,知道常用的输入输出适配器是必不可少的,因为现实模型往往不是一条EPL语句就可以建模的,而是多条EPL连起来,多种流、多种操作连起来的。

3.1声明输入(程序输入或级联输入)

com.sodbase.inputadaptor.StubInputAdaptor

参数1:流名称

本身不产生数据,声明数据要从程序中输入或从其它EPL的输出级联输入。

3.2定时、延时事件输出

3.2.1定时事件生成

com.sodbase.inputadaptor.timer.TimerInputAdaptor

参数1:流名称

参数2:开始时间,需要在当前时间后 格式"yyyy-MM-dd HH:mm:ss"

参数3:周期

还有一个没有开始时间的定时事件

com.sodbase.inputadaptor.TimerInputAdaptor

参数1:流名称

参数2:周期


3.2.2定时延时

com.sodbase.outputadaptor.timer.FixedDelayTimerOutputAdaptor

参数1:延时事件生成的流名称

参数2:延时时间

例:为callstream中的eventtype ='start'的事件生成延时事件,形成新的流calltimeoutevent

CREATE QUERY calltimeout
SELECT 'timer' AS type,T1.time AS time,T1.callerid  AS callerid
FROM T1:callstream
PATTERN T1
WHERE T1.eventtype ='start'
WITHIN 0 

输出适配器配置

<outputAdaptors>
        <isOutputAsSelection>true</isOutputAsSelection>
        <outputAdaptorClassName>com.sodbase.outputadaptor.timer.FixedDelayTimerOutputAdaptor</outputAdaptorClassName>
        <adaptorParams>calltimeoutevent</adaptorParams><!--超时事件流名称为calltimeoutevent-->
        <adaptorParams>1000</adaptorParams><!--超时阈值为1000ms,即超时事件在调用开始后1s时发生-->
        <isExternal>false</isExternal><!--默认false,嵌入式开发没有多用户时一般用不到-->
        <queryName>calltimeout</queryName><!--此输出适配器属于名为calltimeout的EPL-->
</outputAdaptors><span style="font-size:18px;">
</span>

3.2.3变量延时

com.sodbase.outputadaptor.timer.DelayTimerOutputAdaptor

参数1:延时事件生成的流名称

参数2:确定延时时间的字段,即延时长短由事件中的字段值决定

在信息系统中做事件引擎可用于流程审批用户自定义超时跳转,在算法交易中可用于定时交易或拆单策略


3.3 屏幕打印输出

com.sodbase.outputadaptor.PrintEventOutputAdaptor

参数:无

用于将事件打印在屏幕上,一般在单元测试和调试中使用

3.4 一个事件生成多个事件

com.sodbase.outputadaptor.eventsplit.EventSplitOutputAdaptor

参数1: inputStreamConnected,生成的流名称
参数2: spawnnumber : 一个事件生成多少个事件
参数3: idfieldname : 生成的事件中哪个字段用户编号(0..n)
参数4: retainFields : true or false ,是否保留原事件的字段

在算法交易中可用于拆单策略,定时数据处理中,可用于数据拆分处理。


3.5 级联输出适配器

即一个EPL的输出作为另一个EPL的输入

3.5.1 不带watermark级联输出

com.sodbase.outputadaptor.connection.ConnectToSodInputOutputAdaptor

作用:所有事件按原样输出,不做watermark过滤

参数1:级联到的流名称


3.5.2 带watermark级联输出

带watermark的级联输出可以保证只执行一次,乱序容错,watermark值以时间戳的单位时间来衡量,设置的越大,对于乱序和重复事件的容忍度越大,

com.sodbase.outputadaptor.ConnectToSodInputOutputAdaptor

参数1:级联到的流名称

参数2:Watermark长度


3.6 socket输入输出适配器

用于分布式集群计算。EPL分布在不同的机器上,一个EPL通过socket输出连到多个下游EPL,就可以当做分发器(dispatcher)。多个EPL接到同一个EPL的socket输入上,就可以实现结果汇总。

3.6.1socket输入

com.sodbase.inputadaptor.SocketInputAdaptor
参数1:流名称
参数2:监听的端口

3.6.2 可变地址socket输出

com.sodbase.outputadaptor.socket.AddressVarySocketOutputAdaptor
参数1: ip:port(可用?{字段名})
参数2:fail retry的次数


4. 如何运行EPL

运行EPL的方式可参考视频教程总结概括有以下几种

(1)写代码使用API调用

参考SODBASE CEP学习(二):运行第一个EPL例子

(2)用Studio建模后,导出.soddata2文件,将其部署到SODBASE Server

参考 

SODBASE CEP学习(四):类SQL语言EPL与Storm或jStorm集成

(3)将建好的模型和其它分布式框架集成使用

参考SODBASE CEP学习(三):GUI建模工具SODBASE Studio和CEP服务器

开发者社区活动,SODBASE产品的用户现在可以领礼品 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值