EPL语法概述
- 1、EPL 的标准语法骨架
- 2、基础过滤与事件流选择(SELECT & FROM & WHERE)
- 3、数据窗口语法(Data Windows)—— 流处理的物理边界
- 4、分组与聚合(GROUP BY & HAVING)
- 5、高级时序控制:输出频率限制(OUTPUT)
- 6、核心杀手级语法:模式匹配(Pattern)
- 7、总结
在 Esper 中,EPL(Event Processing Language,事件处理语言) 是整个引擎的灵魂。它的语法虽然极度神似传统数据库的 SQL,但其底层的执行逻辑却恰恰相反。
传统 SQL 的本质是“数据静止,查询在动”(数据老老实实呆在磁盘里,你发送一条 SQL 查一次);而 EPL 的本质是“查询静止,数据在流”(EPL 规则像一张网一样死死挂在内存里,数据像瀑布一样冲过去,一旦命中立刻触发回调)。
1、EPL 的标准语法骨架
一条完整的、复杂的 EPL 语句通常包含以下结构:
[@Annotation]SELECTselect_listFROMevent_stream_expression[.win:window_spec][ASalias][WHEREsearch_conditions][GROUPBYgroup_by_expression][HAVINGhaving_conditions][OUTPUT output_spec]乍一看与 SQL 一模一样,但请注意,其中win:window_spec(窗口) 和OUTPUT(输出控制) 是传统 SQL 绝不可能存在的流处理核心特有语法。
2、基础过滤与事件流选择(SELECT & FROM & WHERE)
这是 EPL 最基本的形态,用于从源源不断的事件流中筛选出符合条件的数据。
示例 :简单无状态过滤
@name('VIP-Order-Filter')selectorderId,price,buyerNamefromOrderEventwhereprice>10000andregion='CN'@name(...):注解。给这条 EPL 命名,方便在 Java 代码中通过名字精准捞出对应的 EPStatement。OrderEvent:这是你注册到引擎里的事件别名(通常对应一个 Java Bean)。执行逻辑:没有任何内存缓存。每当一条 OrderEvent 流入,只要价格大于 10000 且地区是 CN,就立刻向外发射结果。
3、数据窗口语法(Data Windows)—— 流处理的物理边界
窗口主要分为两大流派:滑动窗口(Sliding) 和 滚动/翻转窗口(Tumbling/Batch)。
1. 滑动窗口:数据有进有出,位置不断向前推移
win:time(时间大小):只保留最近一段时间内的数据。
-- 统计最近 10 秒内,流入的刷卡事件的总金额selectsum(amount)fromCardPayEvent.win:time(10sec)win:length(条数大小):只保留最近的固定 N 条数据。
-- 统计最近 3 条温度数据的平均值selectavg(temperature)fromTempEvent.win:length(3)2. 滚动/翻转窗口:攒满一批,打包处理,然后清空
win:time_batch(时间周期):每隔固定时间倒出来统计一次。
-- 每隔 1 分钟,统计一次这 1 分钟内所有登录失败的用户数selectcount(*)fromLoginFailEvent.win:time_batch(1min)win:length_batch(条数):凑够固定条数再触发。
-- 每积攒够 100 条日志,打包输出一次日志级别分类统计selectlogLevel,count(*)fromLogEvent.win:length_batch(100)groupbylogLevel4、分组与聚合(GROUP BY & HAVING)
当流数据配合窗口使用时,GROUP BY 的威力才真正显现。
selectdeviceId,max(value)asmaxValfromSensorEvent.win:time(5min)groupbydeviceIdhavingmax(value)>100内存运作:Esper 会在内存里为每一个不同的 deviceId 维护一个长达 5 分钟的滑动窗口。触发时机:只要任何一个设备的 5 分钟内最大值超过了 100,就会向外发射。注意:这里的 group by 会隐式占用内存。如果设备源源不断且几万个设备以后再也不发数据了,需要配合 Context(上下文)来及时释放过期设备的内存,否则会导致内存泄漏。
5、高级时序控制:输出频率限制(OUTPUT)
在传统 SQL 中,算完了直接一把梭给出来。但流处理不行。比如“最近 1 小时内”,每流过一条数据,滑动窗口的值都在变,如果你绑定了监听器,监听器一秒钟会被轰炸几万次。
OUTPUT关键字就是用来给输出结果做限流和定时定量外发的。
selectsymbol,avg(price)fromStockTick.win:time(1hour)groupbysymbol outputlastevery5secoutput last every 5 sec:每隔 5 秒钟,才把过去 1 小时滑动窗口算出来的最后一条(最新一条)统计结果发送给 Java 监听器。中间那 5 秒内产生的无数动态变化直接在内存里悄悄更新,不轰炸外部系统。常用策略:output first(只要周期内的第一条)、output last(只要最新的)、output all(把这 5 秒内产生的所有中间结果打包一起发)。
6、核心杀手级语法:模式匹配(Pattern)
这是 EPL 最难、但也是最强悍的地方。它不用from Event.win:的传统格式,而是使用from pattern [...]语法,专门用来抓取“先发生 A,接着发生 B,中间不能发生 C”这种因果和时序关系。
核心符号:
->:跟随操作符(A 发生后,接着发生 B)。every:循环修饰符。如果没有 every,该规则触发一次后就会在内存里销毁。timer:within(时间):时间沙漏限制。
示例:电商防刷(频次因果)
用户(userId)在一分钟内连续 3 次登录失败,接着立刻发生了一笔大额消费。
selecta.userId,b.amountfrompattern[every(a=LoginEvent(success=false)->LoginEvent(userId=a.userId,success=false)->LoginEvent(userId=a.userId,success=false)->b=OrderEvent(userId=a.userId,amount>5000))wheretimer:within(1min)]示例:物联网故障检测(状态缺失检查)
传感器启动了(Status=START),但在接下来的 10 秒钟内,没有收到任何心跳上报(Status=PING)。
- 解读:
and not配合timer:interval可以在指定时间内一旦没有等到预期事件时,精准触发超时告警。
7、总结
- Getter 强依赖:EPL 里写 select name,底层的 Java 类必须有标准的 public String getName()。
- 避免无边界的 GROUP BY:千万别在没有加 win:time 窗口的流上直接用 group by。这会导致 Esper 认为你要把从开机到关机所有的历史数据都按 Key 存在内存里,用不了几天 JVM 就会直接 OutOfMemoryError。
- Pattern 与普通 EPL 的区别:
- 普通的 EPL(
from MyEvent.win:time)侧重于数据量的统计和计算(算平均值、最大值)。 - Pattern EPL(
from pattern [...])侧重于时序的触发和警报(捕捉特定行为轨迹)。
- 普通的 EPL(