Apache Flink是一个计算框架,地位和Spark差不多。里面的API也有与Spark类似的,例如FlinkKafkaConsumer010对应着Spark里的读取Kafka形成流的API,DataStream对应着Spark里的DStream,也有一系列的transform API例如map/fliter等等。
在yarn上提交任务的方式也十分简洁:
请注意,它的yarn提交模式中只有yarn-cluster;不像spark那样,有yarn-client和yarn-cluster。
乍一看,简直不知道为何会有一个与已存在的框架功能相同的重复框架存在。其实,Apache Flink最亮眼的地方,主要在于它的CEP库(即Complex Event Processing库)。
CEP库允许你在流上定义一系列的模式(pattern),最终使得你可以方便的抽取自己需要的重要的事件出来。
使用Apache Flink主要是用它来进行关联分析,大量的关联分析问题可以归结于followed by问题,而这种时序性的问题又与时间字段息息相关。Apache Flink是如何来处理时间问题呢?它定义了如下3种时间:
可以在定义environment时指定需要用的时间类型:
如上图所示,即是指定使用事件时间。
既然是事件时间,那么就需要指定事件时间是用的数据里的哪个字段。Flink要求时间都得是毫秒级时间戳。指定Kafka流中的时间的方式如下代码所示:
val source = new FlinkKafkaConsumer010[String]("mytopic",new SimpleStringSchema(),properties)
//定义kafka source使用的时间字段
source.assignTimestampsAndWatermarks(new AscendingTimestampExtractor[String] {
override def extractAscendingTimestamp(element: String): Long = {
var rtn = -1L
try{
rtn = JSON.parseObject(element).getJSONObject("payload").getLong("timestamp")*1000
}catch{
case ex:Exception => {
ex.printStackTrace()
}
}
rtn
}
})
在默认不指定使用的时间类型的情况下,Flink默认是用的处理时间(Processing Time)。如果需要指定是事件时间,如上使用递增时间生成器(AscendingTimestampExtractor),那么需要把保证取到的时间字段的值确实是单调递增的,否则会触发Flink的不满足单调递增的warning。
鉴于Kafka里的数据在消费时时间(数据内的时间)已经不是完全有序的了,在使用处理时间的情况下可能followed by模式结果会存在一定的误差。
类型 | API | 含义 |
量词API | times() | 模式发生次数 示例: pattern.times(2,4),模式发生2,3,4次 |
timesOrMore() oneOrMore() |
模式发生大于等于N次 示例: pattern.timesOrMore(2),模式发生大于等于2次 |
|
optional() | 模式可以不匹配 示例: pattern.times(2).optional(),模式发生2次或者0次 |
|
greedy() | 模式发生越多越好 示例: pattern.times(2).greedy(),模式发生2次且重复次数越多越好 |
|
条件API | where() | 模式的条件 示例: pattern.where(_.ruleId=43322),模式的条件为ruleId=433322 |
or() | 模式的或条件 示例: pattern.where(_.ruleId=43322).or(_.ruleId=43333),模式条件为ruleId=43322或者43333 |
|
util() | 模式发生直至X条件满足为止 示例: pattern.oneOrMore().util(condition)模式发生一次或者多次,直至condition满足为止 |
API | 含义 |
next() | 严格的满足条件 示例: 模式为begin("first").where(_.name='a').next("second").where(.name='b') 当且仅当数据为a,b时,模式才会被命中。如果数据为a,c,b,由于a的后面跟了c,所以a会被直接丢弃,模式不会命中。 |
followedBy() | 松散的满足条件 示例: 模式为begin("first").where(_.name='a').followedBy("second").where(.name='b') 当且仅当数据为a,b或者为a,c,b,,模式均被命中,中间的c会被忽略掉。 |
followedByAny() | 非确定的松散满足条件 模式为begin("first").where(_.name='a').followedByAny("second").where(.name='b') 当且仅当数据为a,c,b,b时,对于followedBy模式而言命中的为{a,b},对于followedByAny而言会有两次命中{a,b},{a,b} |
within() | 模式命中的时间间隔限制 |
notNext() notFollowedBy() |
后面的模式不命中(严格/非严格) |
忽略策略 | 含义 |
NO_SKIP | 不忽略 在模式为:begin("start").where(_.name='a').oneOrMore().followedBy("second").where(_.name='b') 对于数据:a,a,a,a,b 模式匹配到的是:{a,b},{a,a,b},{a,a,a,b},{a,a,a,a,b} |
SKIP_PAST_LAST_EVENT | 在模式匹配完成之后,忽略掉之前的部分匹配结果 在模式为:begin("start").where(_.name='a').oneOrMore().followedBy("second").where(_.name='b') 对于数据:a,a,a,a,b 模式匹配到的是:{a,a,a,a,b} |
SKIP_TO_FIRST | 在模式匹配完成之后,忽略掉第一个之前的部分匹配结果 |
SKIP_TO_LAST | 在模式匹配完成之后,忽略掉最后一个之前的部分匹配结果 在模式为:begin("start").where(_.name='a').oneOrMore().followedBy("second").where(_.name='b') 对于数据:a,a,a,a,b 模式匹配到的是:{a,b},{a,a,b},{a,a,a,a,b} |
主机被Netcore/Netis漏洞利用成功随即发起Gafgyt通信行为。
l 前置条件:主机作为dip触发41472 Netcore / Netis 路由器后门告警
l 后续条件:主机作为sip触发41533 Gafgyt僵尸网络通信通信告警
两个条件具备时序关系,且中间的间隔时间不应大于30分钟。
定义Pattern:
将Kafka input和pattern组合成为Pattern Stream。然后再在select方法中合并满足first pattern的告警与满足second pattern的告警,把他们合并为一条“目的IP被Netcore/Netis攻击成功并开始进行Gafgypt通信”告警输出。
输出的效果如下图所示:
需要引入的pom依赖如下:
org.apache.flink
flink-streaming-scala_2.11
1.6.1
org.apache.flink
flink-scala_2.11
1.6.1
org.apache.flink
flink-connector-kafka-0.10_2.11
1.6.1
org.apache.flink
flink-cep-scala_2.11
1.6.1
完整示例代码如下:
package com.flinklearn.main
import java.util.Properties
import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.cep.PatternSelectFunction
import org.apache.flink.cep.nfa.aftermatch.AfterMatchSkipStrategy
import org.apache.flink.cep.pattern.conditions.IterativeCondition
import org.apache.flink.cep.scala.CEP
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.functions.timestamps.{BoundedOutOfOrdernessTimestampExtractor}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010
import org.apache.flink.streaming.api.scala._
object Main {
def main(args:Array[String]):Unit = {
val env:StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val properties = new Properties()
properties.setProperty("bootstrap.servers", "bootstrap")
properties.setProperty("group.id","com.flinklearn.main.Main")
val source = new FlinkKafkaConsumer010[String]("mytopic",new SimpleStringSchema(),properties)
val input:DataStream[JSONObject] = env.addSource(source)
.map(line=>{
var rtn:JSONObject = null
try{
rtn = JSON.parseObject(line).getJSONObject("payload")
}catch{
case ex:Exception => {
ex.printStackTrace()
}
}
rtn
}).filter(line=>line!=null)
val pattern = Pattern.begin[JSONObject]("first",AfterMatchSkipStrategy.skipPastLastEvent).where(new IterativeCondition[JSONObject] {
override def filter(value: JSONObject, ctx: IterativeCondition.Context[JSONObject]): Boolean = {
//目标主机被netcore漏洞扫描
//41472 Netcore / Netis 路由器后门
value.getLong("ruleid").equals(41472L)
}
}).oneOrMore.greedy.followedBy("second").where(new IterativeCondition[JSONObject] {
override def filter(value: JSONObject, ctx: IterativeCondition.Context[JSONObject]): Boolean = {
//主机主动发起gafgyt通信行为
//41533 Gafgyt僵尸网络通信
val iterator:java.util.Iterator[JSONObject] = ctx.getEventsForPattern("first").iterator()
var tag = false
if(value.getLong("ruleid").equals(41533L)){
while (!tag&&iterator.hasNext){
val curitem = iterator.next()
if(curitem.getString("dip").equals(value.getString("sip")) && value.getLong("timestamp") > curitem.getLong("timestamp")){
tag = true
}
}
}
tag
}
}).within(Time.minutes(30L))
val patternStream = CEP.pattern(input,pattern)
val result = patternStream.select(new PatternSelectFunction[JSONObject,JSONObject] {
override def select(pattern: java.util.Map[String, java.util.List[JSONObject]]): JSONObject = {
val first = pattern.get("first")
val second = pattern.get("second")
var startTime = first.get(0).getLong("timestamp")
var endTime = second.get(0).getLong("timestamp")
for(i <- 1 until first.size()){
if(first.get(i).getLong("timestamp") < startTime){
startTime = first.get(i).getLong("timestamp")
}
}
for(i <- 1 until second.size()){
if(second.get(i).getLong("timestamp") > endTime){
endTime = second.get(i).getLong("timestamp")
}
}
val sip = first.get(0).getString("sip")
val dip = first.get(0).getString("dip")
val info1 = second.get(0).getString("dip")
val msg = "目的IP被Netcore/Netis攻击成功并开始进行Gafgypt通信"
val obj:JSONObject = new JSONObject()
obj.put("start_time", startTime)
obj.put("end_time", endTime)
obj.put("sip", sip)
obj.put("dip", dip)
obj.put("info1", info1)
obj.put("msg", msg)
obj
}
})
result.print()
env.execute("Event generating test")
}
}