最近一直有同事跟我说目前开发的数据流平台仅仅只是把数据推送过来作用不大。希望最好能够连数据分析也一起做了,告诉他们结果就好。这样的需求一般交给数据分析组去做就好了,不过了解了一下现在只有离线分析,最快也只能半小时统计一次,实时分析这块还没有实现。
去搜了下,看看有哪些开源的实时分析引擎可以用。之前先看了下storm,twitter做的,大公司大品牌,完全开源,看上去的确不错。但是看了一下功能,相当于是一个实时hadoop,只能帮你计算,但是怎么算还得你自己写程序,离我想要的功能上有一些距离。目前想使用实时分析的产品一大堆,大部分产品数据量都不大,每个都给他们写段分析代码,加上调试,能累死人,维护代价高。
之后发现了Esper,仔细看了下,还真不错,他是一个强大的支持ESP(
Event Stream Process
)和CEP(
Complex Event Process )分析的引擎,特别是他的EPL解析语言吸引了我,可以通过简单的写一条类似SQL一样的语句完成统计。
国内对esper研究的资料很少,因此就从 http://esper.codehaus.org/官网下载了esper的源码和文档,仔细研究了一下,接下来分享一下我对esper的学习心得。
首先介绍一下ESPER的大体结构,esper从内容上分为两块,esper的核心esper-4.x.x.jar和esper-io。
(1)esper的核心包包含了EPL语法解析引擎,事件监听机制,事件处理等核心模块。
(2)esper的io包含从各种数据源读取数据以及将输出结果写入各种数据源,包括excel,database,JMS,http,socket,XML。
贴一张esper官网上的结构图,方便大家了解esper的结构
接下来对上述结构图进行详细的解释让大家加深对ESPER的了解
1. Event对象:ESPER处理的事件的最小单位,一个任意的JavaBean对象,属性支持简单的java类型、数组、map、以及嵌套JavaBean,很灵活,下面是一个简单的Event对象
public class OrderEvent { private String itemName; private double price; public OrderEvent(String itemName, double price) { this.itemName = itemName; this.price = price; } public String getItemName() { return itemName; } public double getPrice() { return price; } }
2.EPL:
EPL是ESPER的核心,它类似于SQL,但是和SQL的执行方式不同。
SQL是数据在那里,你每次执行SQL就会触发一次查询;而EPL是查询在这里,数据输入达到一定条件即可触发查询。
这个条件可以有多种:
a).每个event对象来就触发一次查询,并只处理当前对象
这个EPL语句会在每个OrderEvent对象到达后,并将该event交给后续的Listener(后面会降到)来进行处理。但是这种用法不多见,意义不大。
b).窗口处理模式:
EPL最大的特色就是这个窗口处理模式,有两种窗口,时间窗口和长度窗口。
时间窗口 : 大家想一下,如果有一个场景,要获取最近3秒内OrderEvent的price的平均值,那该怎么做呢?一般的做法需要做个后台线程来做3秒的时间统计,时间到了再做后续处理,虽然不复杂,但是也挺繁琐的。
看看EPL是怎么做的
select avg(price) from test.OrderEvent.win:time(3 sec)
win:time(3 sec)就是定义了3秒的时间窗口,
avg(price)就是统计了3秒内的OrderEvent对象的price的平均值
长度窗口: 长度窗口和时间窗口比较类似
select avg(price) from test.OrderEvent.win:length(100)
win:length(10)就是定义了10个Event的,
avg(price)就是统计了最近10个的OrderEvent对象的price的平均值
以上这些都比较容易理解,虽然知道了处理方法,也比较好用,我还是比较喜欢钻研一下他的内部实现方式。先来看一张时间窗口模式的图
他仅保留最近时间窗口的对象内容,但是每个Event到来都会触发一次UpdateListener的操作
EPL语句会作为一个Statement来监听事件的到来,当New Events有新事件时就会触发UpdateListener的操作,下面是一个updateListener的简单例子,
event.get("avg(price))就可以获得EPL查询所获得的price平均值,然后就可以加入自己的代码进行处理,比如将结果写入本地文件
而New Events和Old Events就是他的输入,而ave(price)操作所计算的对象就是Length Window中的内容。
public class MyListener implements UpdateListener { public void update(EventBean[] newEvents, EventBean[] oldEvents) { EventBean event = newEvents[0]; System.out.println("avg=" + event.get("avg(price)")); } }
事件窗口也基本类似。
EPL的时间窗口的计时是怎么实现的呢?我们来看下他的源码
ScheduledThreadPoolExecutor timer;//省略构造
timerTask = new EPLTimerTask(timerCallback);
ScheduledFuture> future = timer.scheduleAtFixedRate(timerTask, 0, msecTimerResolution, TimeUnit.MILLISECONDS);//估计每100毫秒运行一次
...
_lastDrift = Math.abs(future.getDelay(TimeUnit.MILLISECONDS));//计算延迟
...
CurrentTimeEvent currentTimeEvent = new CurrentTimeEvent(msec); sendEvent(currentTimeEvent);//发送时间控制Event
贴得比较乱 简单解释一下,
ScheduledThreadPoolExecutor
.scheduleAtFixedRate固定每100ms(可配置)运行一次,并且通过计算延迟
future.getDelay来确保计时精确,接下来通过发送一个
CurrentTimeEvent来推送时间前进100+delay(ms),也就是说ESPER中的时间不是完全受机器时间控制的,而是通过发送TimeEvent由应用来进行控制的,这方便做很多的扩展。
c)批量窗口处理模式
窗口模式是会在每个Event来都触发一次UpdateListener操作,如果每秒Event数量达到很大的话这种方式明显是不行的 CPU消耗会很厉害
批量窗口处理模式正好可以解决这个问题
批量时间窗口模式
select avg(price) from test.OrderEvent.win:time_batch(3 sec)
批量长度窗口模式
select avg(price) from test.OrderEvent.win:length_batch(10)
时间批量模式的操作图如下
上图的时间窗口大小为4s,他会在4s的窗口时间到达以后才将窗口中的内容一起扔给UpdateListener来进行处理,性能相对节约很多,特别是大数据量的情况下。长度批量窗口的处理模式也是类似。
上述窗口模式下
内存使用情况又是如何呢?经过本人测试和研究代码发现,它会保留两个窗口的内存使用量,一个保存当前窗口的Events,一个保存上一个窗口的Events,因此在估算一个数据分析程序占用多少内存要看上面监听的EPL语句开的窗口的大小以及数据的TPS,防止内存OOM。
掌握了上面的窗口的概念,后面其他的内容都很好理解了
d) 过滤
where过滤
select avg(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
having过滤
select avg(price) from test.OrderEvent.win:time_batch(3 sec) having price>10
似曾相识啊,执行方式也基本和SQL里的where 和 having差不多。
在EPL里where 是在incoming Events到window之间进行过滤,having是在window到New Eventing之间进行过滤
e)聚合
count
select count(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
sum
select sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
group by
select itemName,sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10 group by itemName
都很简单,了解SQL的都狠容易上手
f) 函数
ESPER默认加载
java.lang.*
java.math.*
java.text.*
java.util.*
支持这些包下的函数方法,例如
select Math.round(sum(price)) from test.OrderEvent.win:time_batch(3 sec) where price>10
它还支持自定义函数,举个例子,做个计算百分比的函数
public class Util { public static double computePercent(double amount, double total) { return amount / total * 100; } }
配置一下
name="percent" function-class="mycompany.MyUtilityClass" function-method="computePercent" />
OK了,可以用了
select percent(price,total) from OrderEvent
总体来说,ESPER的EPL功能非常强大,而且基本和SQL类似,入门容易,构造一个实时数据分析系统比较简单,且维护成本低,新应用进来只需要简单配置一下EPL语句就可以了,方便快捷,对大部分的系统还是比较适合的。