flink cep pattern動態加載

通常我們在提交一個flink cep任務,流程基本上是:開發,打包,部署;例如我們有一個任務:計算在60秒內,連續兩次登陸失敗的用戶

begin("begin").where(_.status=='fail').next("next").where(_.status=="fail").within(Time.seconds(60))

然後又來一個任務:計算60秒內,用戶登陸失敗1次,然後第二次登陸成功的用戶

begin("begin").where(_.status=='fail').next("next").where(_.status=="success").within(Time.seconds(60))

這兩個任務,數據的輸入,輸出都是一樣的,唯一的區別就是pattern不同;往常的話我們要重複之前的3個步驟才能完成任務的上線;如果我們能根據flink 任務傳入參數,動態生成pattern對象,就能簡化任務的上線流程,畫個圖

 

如何實現pattern動態加載?爲了實現這個功能,可以拆分成兩個步驟

1.根據pattern規則轉換成pattern字符串
val patternStr="begin(\"begin\").where(_.status==\"fail\")
        .next(\"next\").where(_.status==\"fail\").within(Time.seconds(60))"
2.將pattern字符串轉換成pattern對象
val pattern = transPattern(patternStr)

現在先看第2步,假定現在有了patternStr,如何轉成pattern對象?

str->obj,第一個能想到的方案就是使用javax.script.ScriptEngine調用groovy腳本的方法生成pattern對象;

groovy 腳本如下:

import com.hhz.flink.cep.pojo.LoginEvent
import com.hhz.flink.cep.patterns.conditions.LogEventCondition
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.windowing.time.Time
def getP(){
    return Pattern.<LoginEvent>begin("begin")
    .where(new LogEventCondition("getField(eventType)==\"fail\""))
    .next("next").where(new LogEventCondition("getField(eventType)==\"fail\""))
    .times(2).within(Time.seconds(3))
}

這個腳本可以以字符串的方式通過groovy腳本引擎加載到內存中,並使用invokeFunction調用getP()方法,就可以返回pattern對象,僞代碼如下

String script="def getP(){return Pattern.<>....within(Time.seconds(3)))}";
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine =  factory.getEngineByName("groovy");
engine.eval(script);
Invocable inv = (Invocable) engine;
Pattern<LoginEvent, LoginEvent> pattern
    = (Pattern<LoginEvent, LoginEvent>) invocable.invokeFunction("getP");

現在回過頭來看第一步:根據pattern規則轉換成pattern字符串

在scala中pattern代碼如下

begin("begin").where(_.status=='fail').next("next").where(_.status=="fail").within(Time.seconds(60))

where方法可以接受表達式,例如"_.status=='fail'",同時他也可以接受一個SimpleCondition對象,例如

where(new SimpleCondition<LoginEvent>() {
            @Override
            public boolean filter(LoginEvent event) {
                return event.eventType() == "fail";
            }
        })

但在groovy中不支持接受"_.status=='fail'"表達式作爲函數的參數,所以在生成pattern串是必須將where中的表達式換成SimpleCondition對象;

那問題來了,換成SimpleCondition對象後,我們就得在filter方法中實現表達式的邏輯;這顯然不是我們所需要的,這樣做的話,patternStr的維護的成本就太高了;如果我們將表達式以字符串的形式傳入到SimpleCondition對象中,然後在filter中自動計算表達式的值,就像

where(new LogEventCondition("getField(eventType)==\"fail\""))

filter方法根據表達式getField(eventType)==\"fail\"計算結果,返回ture或false,難點來了,如何根據表達式計算結果,這裏就需要引入aviator包,關於aviator我們看幾個樣例

import com.googlecode.aviator.AviatorEvaluator;
public class TestAviator {
    public static void main(String[] args) {
        Long result = (Long) AviatorEvaluator.execute("1+2+3");
        System.out.println("-------"+result);
    }
}
結果輸出:
-------6

具體看下LogEventCondition

public class LogEventCondition  extends SimpleCondition<LoginEvent> implements Serializable {
    private String script;

    static {
        AviatorEvaluator.addFunction(new GetFieldFunction());
    }

    //getField(eventType)==\"fail\"
    public LogEventCondition(String script){
        this.script = script;
    }

    @Override
    public boolean filter(LoginEvent value) throws Exception {
        Map<String, Object> stringObjectMap = Obj2Map.objectToMap(value);
        //計算表達式的值
        boolean result = (Boolean) AviatorEvaluator.execute(script, stringObjectMap);
        return result;
    }

}

到這裏,cep pattern動態加載就介紹完了;起初我也是想像某些大廠一樣,通過訂製一套特有的DSL語法,然後將DSL語句解析轉換成pattern,這樣的話,非開發同學也就能夠在公司的數據平臺直接訂製實時計算任務;但回想之前給非研發同學培訓sql的悲慘經歷,放棄了;覺得pattern訂製的學習成本還是有點高,交給運營或者產品去搞這事,不靠譜,所以這事還是得研發來幹;對於研發來講pattern最原生的規則就是最好的~~~

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章