Flink之CEP詳解
一、是什麼
維基百科對CEP的定義爲:“CEP是一種事件處理模式,它從若干源中獲取事件,並偵測複雜環境的事件或模式,CEP的目的是確認一些有意義的事件(比如某種威脅或某種機會),並儘快對其作出響應”。總結一下也就是CEP是一個事件處理模式,當某項檢測需要在多源且複雜的事件流中進行處理,並需要低延遲、秒級或毫秒級的響應時,我們就可以考慮用到它。市場上有多種CEP的解決方案,例如Spark、Samza、Beam等,但他們都沒有提供專門的library支持。但是Flink提供了專門的CEP library。
二、 Flink CEP詳解
Flink中實現一個CEP可以總結爲四步:
- 構建需要的數據流
- 構造正確的模式
- 將數據流和模式進行結合
- 在模式流中獲取匹配到的數據
其中第一步和第三步一般會是標準操作,核心在於第二部構建模式,需要利用Flink CEP支持的特性,構造出正確反映業務需求的匹配模式。
2.1 詳解Flink CEP library
Flink爲CEP所提供的Flink CEP library包含如下組件:
Event Stream
pattern定義
pattern檢測
生成Alert
首先,開發人員要在DataStream流上定義出模式條件,之後Flink CEP引擎進行模式檢測,必要時生成告警。
爲了使用Flink CEP,我們需要導入依賴:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
2.1.1 Event Streams
以登陸事件流爲例:
case class LoginEvent(userId: String, ip: String, eventType: String, eventTime: String)
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setParallelism(1)
val loginEventStream = env.fromCollection(List(
LoginEvent("001", "192.168.0.101", "fail", "1558430842"),
LoginEvent("001", "192.168.0.102", "fail", "1558430843"),
LoginEvent("001", "192.168.0.103", "fail", "1558430844"),
LoginEvent("002", "192.168.10.104", "success", "1558430845")
)).assignAscendingTimestamps(_.eventTime.toLong)
2.1.2 Pattern API
每個Pattern都應該包含幾個步驟,或者叫做state。從一個state到另一個state,通常我們需要定義一些條件,例如下列的代碼:
val loginFailPattern = Pattern.begin[LoginEvent]("begin")
.where(_.eventType.equals("fail"))
.next("next")
.where(_.eventType.equals("fail"))
.within(Time.seconds(10)
每個state都應該有一個標示:例如.begin[LoginEvent]("begin")
中的"begin"
每個state都需要有一個唯一的名字,而且需要一個filter來過濾條件,這個過濾條件定義事件需要符合的條件,例如:
.where(_.eventType.equals("fail"))
我們也可以通過subtype來限制event的子類型:
start.subtype(SubEvent.class).where(...);
事實上,你可以多次調用subtype和where方法;而且如果where條件是不相關的,你可以通過or來指定一個單獨的filter函數:
pattern.where(...).or(...);
之後,我們可以在此條件基礎上,通過next或者followedBy方法切換到下一個state,next的意思是說上一步符合條件的元素之後緊挨着的元素;而followedBy並不要求一定是挨着的元素。這兩者分別稱爲嚴格近鄰和非嚴格近鄰。
val strictNext = start.next("middle")
val nonStrictNext = start.followedBy("middle")
最後,我們可以將所有的Pattern的條件限定在一定的時間範圍內:
next.within(Time.seconds(10))
這個時間可以是Processing Time,也可以是Event Time。
注:有人可能會說API介紹的不夠詳細,所以這裏推薦一篇關於Pattern API詳解的博客
2.1.3 Pattern 檢測
通過一個input DataStream以及剛剛我們定義的Pattern,我們可以創建一個
PatternStream:
val input = ...
val pattern = ...
val patternStream = CEP.pattern(input, pattern)
val patternStream = CEP.pattern(loginEventStream.keyBy(_.userId), loginFailPattern)
一旦獲得PatternStream,我們就可以通過select或flatSelect,從一個Map序列找到我們需要的警告信息。
2.1.4 生成Alert
2.1.4.1 select
select方法需要實現一個PatternSelectFunction,通過select方法來輸出需要的警告。它接受一個Map對,包含string/event,其中key爲state的名字,event則爲真實的Event。
val loginFailDataStream = patternStream
.select((pattern: Map[String, Iterable[LoginEvent]]) => {
val first = pattern.getOrElse("begin", null).iterator.next()
val second = pattern.getOrElse("next", null).iterator.next()
Warning(first.userId, first.eventTime, second.eventTime, "warning")
})
其返回值僅爲1條記錄。
2.1.4.2 flatSelect
通過實現PatternFlatSelectFunction,實現與select相似的功能。唯一的區別就是flatSelect方法可以返回多條記錄,它通過一個Collector[OUT]
類型的參數來將要輸出的數據傳遞到下游。