5分钟开启Esper之旅 - Binhua Liu - 博客园
在我之前发表的文章中,我提到我最近热衷于Complex Event Processing (CEP) (复杂事件处理)。简单来说,CEP把数据流作为输入,根据一系列预定义的规则,把数据(或部分数据)重定向给监听者们;又或者是当发现数据中的隐含的模式(Pattern)时,触发事件。在大量数据被产生出来并需要进行实时地分析的场景下,CEP特别有用。
有一个很不错的软件项目,可以让你做到这一点,叫做ESPER。你可以在这里找到该项目的网站。Esper向程序员提供一个称为EPL的语言,有些类似于SQL语言,它可以很好地用于对规则和模式的配置,这些规则和模式将被应用到数据流上。
Esper附带的文档是相当完整的,但缺乏实际的例子,这使得它看起来难以被使用。所以这里给出一个Esper的5分钟指导。虽然下面的例子是用Java编写,但是其实Esper是同时支持Java和C#的。我这里假设你已经下载了Esper,如果没有,可以通过点击这个链接来完成。解压刚才下载的文件后,你应该在你磁盘的某个地方可以找到一个叫esper-3.1.0的文件夹。(译者:这篇文章比较早了,现在esper最新版本是5.2.0)
在开始前,你需要添加几个库文件到你的工程中,当然,esper-3.1.0.jar是其中之一,你也需要另外其他4个第三方的库文件,他们可以在esper-3.1.0.jar/esper/lib文件夹中找到。
现在开始我们的5分钟吧。在你把需要分析的数据丢到CEP引擎的管道中之前,需要把这些数据结构化到对象当中去。让我们用一个简单的例子,写一个类(或者叫它bean)来描述给定时间的某个股票的价格:
import java.util.Date; public static class Tick { String symbol; Double price; Date timeStamp; public Tick(String s, double p, long t) { symbol = s; price = p; timeStamp = new Date(t); } public double getPrice() {return price;} public String getSymbol() {return symbol;} public Date getTimeStamp() {return timeStamp;} @Override public String toString() { return "Price: " + price.toString() + " time: " + timeStamp.toString(); } }
它有3个属性:股票代码,价格和时间戳。在我们开始生成数以亿计的数据前,我们需要通知引擎它需要处理哪些对象,这是通过在实例化CEP引擎时,使用一个Configuration对象来实现的:
import com.espertech.esper.client.*; public class main { public static void main(String [] args){ //The Configuration is meant only as an initialization-time object. Configuration cepConfig = new Configuration(); // We register Ticks as objects the engine will have to handle cepConfig.addEventType("StockTick",Tick.class.getName()); // We setup the engine EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine",cepConfig); } }
作为测试目的,我们现在创建一个函数来生成随机的数据,并且把它们丢到CEP引擎当中,我们把这个函数叫做“GenerateRandomTick”,它把EPRuntime对象作为参数,这个对象用于把事件传递给CEP引擎:
import java.util.Random; import com.espertech.esper.client.*; public class exampleMain { private static Random generator=new Random(); public static void GenerateRandomTick(EPRuntime cepRT){ double price = (double) generator.nextInt(10); long timeStamp = System.currentTimeMillis(); String symbol = "AAPL"; Tick tick= new Tick(symbol,price,timeStamp); System.out.println("Sending tick:" + tick); cepRT.sendEvent(tick); } public static void main(String[] args) { //The Configuration is meant only as an initialization-time object. Configuration cepConfig = new Configuration(); cepConfig.addEventType("StockTick",Tick.class.getName()); EPServiceProvider cep=EPServiceProviderManager.getProvider("myCEPEngine",cepConfig); EPRuntime cepRT = cep.getEPRuntime(); } }
现在,我们有了一个可以工作的CEP引擎,和不断输入的虚假的数据,是时候来创建我们的第一条规则了,用Esper的说法,我们的第一条EPL语句。要这么做,我们需要请引擎的管理员记录我们的语句。然后,CEP引擎会根据EPL语句的定义,过滤它收到的数据,当数据满足语句中的选择条件或者模式时,触发事件。
public static void main(String[] args) { //The Configuration is meant only as an initialization-time object. Configuration cepConfig = new Configuration(); cepConfig.addEventType("StockTick",Tick.class.getName()); EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine",cepConfig); EPRuntime cepRT = cep.getEPRuntime(); // We register an EPL statement EPAdministrator cepAdm = cep.getEPAdministrator(); EPStatement cepStatement = cepAdm.createEPL("select * from " + "StockTick(symbol='AAPL').win:length(2) " + "having avg(price) > 6.0"); }
这里,我们的规则设置为:每当最近的2次数据的平均值大于6.0时,触发事件。
下一步,主要是创建一个监听者并把它和我们的选择规则产生的事件关联起来。可以这么做:
cepStatement.addListener(new CEPListener());
这里有不同的方式来实现监听者,下面的是其中最简单的一种。这里监听者只是简单地把它从引擎中收到的对象打印出来:
public static class CEPListener implements UpdateListener { public void update(EventBean[] newData, EventBean[] oldData) { System.out.println("Event received: " + newData[0].getUnderlying()); } }
到目前为止,看上去还不错。现在是测试我们的代码的时候了。让我们生成一些数据,看一切能否正常工作。可以在main函数中添加一下行来做到:
for(int i = 0; i< 5; i++) GenerateRandomTick(cepRT);
现在所有的代码看上去如下所示(我把Tick类和入口函数放在一起,这样你就能把它们复制粘贴到一个文件并运行它们)
import com.espertech.esper.client.*; import java.util.Random; import java.util.Date; public class exampleMain { public static class Tick { String symbol; Double price; Date timeStamp; public Tick(String s, double p, long t) { symbol = s; price = p; timeStamp = new Date(t); } public double getPrice() {return price;} public String getSymbol() {return symbol;} public Date getTimeStamp() {return timeStamp;} @Override public String toString() { return "Price: " + price.toString() + " time: " + timeStamp.toString(); } } private static Random generator = new Random(); public static void GenerateRandomTick(EPRuntime cepRT) { double price = (double) generator.nextInt(10); long timeStamp = System.currentTimeMillis(); String symbol = "AAPL"; Tick tick = new Tick(symbol, price, timeStamp); System.out.println("Sending tick:" + tick); cepRT.sendEvent(tick); } public static class CEPListener implements UpdateListener { public void update(EventBean[] newData, EventBean[] oldData) { System.out.println("Event received: " + newData[0].getUnderlying()); } } public static void main(String[] args) { //The Configuration is meant only as an initialization-time object. Configuration cepConfig = new Configuration(); cepConfig.addEventType("StockTick", Tick.class.getName()); EPServiceProvider cep = EPServiceProviderManager.getProvider("myCEPEngine", cepConfig); EPRuntime cepRT = cep.getEPRuntime(); EPAdministrator cepAdm = cep.getEPAdministrator(); EPStatement cepStatement = cepAdm.createEPL("select * from " + "StockTick(symbol='AAPL').win:length(2) " + "having avg(price) > 6.0"); cepStatement.addListener(new CEPListener()); // We generate a few ticks... for (int i = 0; i < 5; i++) { GenerateRandomTick(cepRT); } } }
Output:
log4j:WARN No appenders could be found for logger (com.espertech.esper.epl.metric.MetricReportingPath). log4j:WARN Please initialize the log4j system properly. Sending tick:Price: 6.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 0.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 7.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 4.0 time: Tue Jul 21 01:11:15 CEST 2009 Sending tick:Price: 9.0 time: Tue Jul 21 01:11:15 CEST 2009 Event received: Price: 9.0 time: Tue Jul 21 01:11:15 CEST 2009
正如你看到的,只有最后两行数据平均值大于6,因此只有一个事件最终被引擎触发了。相当不错!
Oh,你或许担心输出中的第一行,是的,这里还有一点小问题。事实上,Esper使用的日志生成包log4j导致了这个警告。它是可以通过一个叫log4j.xml的文件来配置的,你可以在esper-3.1.0/examples下的某个例子中的/etc目录下找到它。我不认为给我们所有的程序都弄一个xml配置文件是个好主意,所以我们在以下代码中,用点技巧来配置我们的logger,在你的文件开始处加入些import和代码:
import org.apache.log4j.ConsoleAppender; import org.apache.log4j.SimpleLayout; import org.apache.log4j.Level; import org.apache.log4j.Logger; //and this in the main function before the rest of your code: public static void main(String [] args){ SimpleLayout layout = new SimpleLayout(); ConsoleAppender appender = new ConsoleAppender(new SimpleLayout()); Logger.getRootLogger().addAppender(appender); Logger.getRootLogger().setLevel((Level) Level.WARN); (...)
5分钟到此结束。
下一篇文章中,我将更深入一些来探索EPL语句,提供一些代码来连接两个引擎实现所谓的“事件精化”(event refinement)(译者:好像之后作者再也没有更新过了,所以,不要指望后续了:))
原文地址:https://coffeeonesugar.wordpress.com/2009/07/21/getting-started-with-esper-in-5-minutes/。刘斌华原创翻译,转载请注明出处
参考:http://backend.blog.163.com/blog/static/202294126201252114240408
win:time(3 sec)就是定义了3秒的时间窗口,avg(price)就是统计了3秒内的OrderEvent对象的price的平均值select avg(price) from test.OrderEvent.win:time(3 sec)
win:length(10)就是定义了10个Event的,avg(price)就是统计了最近10个的OrderEvent对象的price的平均值select avg(price) from test.OrderEvent.win:length(100)
having过滤select avg(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
似曾相识啊,执行方式也基本和SQL里的where 和 having差不多。select avg(price) from test.OrderEvent.win:time_batch(3 sec) having price>10
select count(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
group byselect sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10
select itemName,sum(price) from test.OrderEvent.win:time_batch(3 sec) where price>10 group by itemName
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;
}
}
OK了,可以用了<plugin-singlerow-function name="percent"
function-class="mycompany.MyUtilityClass" function-method="computePercent" />
select percent(price,total) from OrderEvent