Apache Flink CEP学习总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rongyongfeikai2/article/details/83656505

1. 简介

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),最终使得你可以方便的抽取自己需要的重要的事件出来。

2.相关API

2.1 时间

使用Apache Flink主要是用它来进行关联分析,大量的关联分析问题可以归结于followed by问题,而这种时序性的问题又与时间字段息息相关。Apache Flink是如何来处理时间问题呢?它定义了如下3种时间:

  • 处理时间(Processing Time): 开始处理此条数据的时间
  • 事件时间(Event Time): 数据的时间(此时间字段应该来源于数据内容本身,使用该类型的时间是需要用时间水印生成器)
  • 进入时间(Ingestion Time): 数据进入Flink的时间

可以在定义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模式结果会存在一定的误差

2.2 单个模式

类型 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满足为止


2.3 联合模式

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()
后面的模式不命中(严格/非严格)

2.4 忽略策略

忽略策略 含义
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}

 

3.示例场景实现

主机被Netcore/Netis漏洞利用成功随即发起Gafgyt通信行为。

l 前置条件:主机作为dip触发41472 Netcore / Netis 路由器后门告警

l 后续条件:主机作为sip触发41533 Gafgyt僵尸网络通信通信告警

两个条件具备时序关系,且中间的间隔时间不应大于30分钟。

 

定义Pattern:

  • 在first pattern中,定义的条件是ruleid=41472。
  • 在second pattern中,定义的条件是ruleid=41533且此条消息的源ip==前置条件的目的ip
  • within方法,限定了满足条件必须在30分钟内
  • oneOrMore().greedy,第一个条件满足一次或者多次,越多次越好
  • 忽略策略为AfterMatchSkipStrategy.skipPastLastEvent,在满足模式之后,忽略掉之前的部分满足条件
    这样对于输入的告警是多次sip=1.1,dip=1.2的netcore告警,随后一条sip=1.2,dip=1.3的gafgyt告警,拿到的命中数据会是{"first":n条netcore告警的list,"second":gafgyt告警}

 

将Kafka input和pattern组合成为Pattern Stream。然后再在select方法中合并满足first pattern的告警与满足second pattern的告警,把他们合并为一条“目的IP被Netcore/Netis攻击成功并开始进行Gafgypt通信”告警输出。

输出的效果如下图所示:

需要引入的pom依赖如下:

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-streaming-scala_2.11</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-scala_2.11</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-connector-kafka-0.10_2.11</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-cep-scala_2.11</artifactId>
  <version>1.6.1</version>
</dependency>

完整示例代码如下:

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")
  }
}

 

展开阅读全文

没有更多推荐了,返回首页