【flink】flink复杂事件处理

复杂事件处理(CEP)是一种基于流处理的技术,将系统数据看作不同类型的事件,通过分析事件之间的关系,建立不同的事件关系序列库,并利用过滤、关联、聚合等技术,最终由简单事件产生高级事件,并通过模式规则的方式对重要信息进行跟踪和分析,从实时数据中发掘有价值的信息。复杂事件处理主要应用于防范网络欺诈、设备故障检测、风险规避和智能营销等领域。Flink 基于 DataStrem API 提供了 FlinkCEP 组件栈,专门用于对复杂事件的处理,帮助用户从流式数据中发掘有价值的信息。

1、CEP 相关概念

配置依赖

<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep-scala_2.11</artifactId>
<version>1.9.1</version>
</dependency>

事件定义

  • 简单事件:简单事件存在于现实场景中,主要的特点为处理单一事件,事件的定义可以
    直接观察出来,处理过程中无须关注多个事件之间的关系,能够通过简单的数据处理手
    段将结果计算出来。
  • 复杂事件:相对于简单事件,复杂事件处理的不仅是单一的事件,也处理由多个事件组
    成的复合事件。复杂事件处理监测分析事件流(Event Streaming),当特定事件发生时
    来触发某些动作。
    复杂事件中事件与事件之间包含多种类型关系,常见的有时序关系、聚合关系、层次关
    系、依赖关系及因果关系等。

2、Pattern API

FlinkCEP 中提供了 Pattern API 用于对输入流数据的复杂事件规则定义,并从事件流中抽取事件结果。包含四个步骤:

  • 输入事件流的创建
  • Pattern 的定义
  • Pattern 应用在事件流上检测
  • 选取结果

模式定义

定义 Pattern 可以是单次执行模式,也可以是循环执行模式。单词执行模式一次只接受一个事件,循环执行模式可以接收一个或者多个事件。通常情况下,可以通过指定循环次数将单次执行模式变为循环执行模式。每种模式能够将多个条件组合应用到同一事件之上,条件组合可以通过 where 方法进行叠加。每个 Pattern 都是通过 begin 方法定义的

val start = Pattern.begin[Event]("start_pattern")

下一步通过 Pattern.where()方法在 Pattern 上指定 Condition,只有当 Condition 满足之后,当前的 Pattern 才会接受事件。

start.where(_.getCallType == "success")

设置循环次数

对于已经创建好的 Pattern,可以指定循环次数,形成循环执行的 Pattern。

  • times:可以通过 times 指定固定的循环执行次数。
//指定循环触发4次
start.times(4);
//可以执行触发次数范围,让循环执行次数在该范围之内
start.times(2, 4);
  • optional:也可以通过 optional 关键字指定要么不触发要么触发指定的次数。
start.times(4).optional();
start.times(2, 4).optional();
  • greedy:可以通过 greedy 将 Pattern 标记为贪婪模式,在 Pattern 匹配成功的前提下,
    会尽可能多地触发。
//触发2、3、4次,尽可能重复执行
start.times(2, 4).greedy();
//触发0、2、3、4次,尽可能重复执行
start.times(2, 4).optional().greedy();
  • oneOrMore:可以通过 oneOrMore 方法指定触发一次或多次。
// 触发一次或者多次
start.oneOrMore();
//触发一次或者多次,尽可能重复执行
start.oneOrMore().greedy();
// 触发0次或者多次
start.oneOrMore().optional();
// 触发0次或者多次,尽可能重复执行
start.oneOrMore().optional().greedy();
 timesOrMor:通过 timesOrMore 方法可以指定触发固定次数以上,例如执行两次以上。
// 触发两次或者多次
start.timesOrMore(2);
// 触发两次或者多次,尽可能重复执行
start.timesOrMore(2).greedy();
// 不触发或者触发两次以上,尽可能重复执行
start.timesOrMore(2).optional().greedy();

定义条件

每个模式都需要指定触发条件,作为事件进入到该模式是否接受的判断依据,当事件中的数值满足了条件时,便进行下一步操作。在 FlinkCFP 中通过 pattern.where()、pattern.or()及 pattern.until()方法来为 Pattern 指定条件,且 Pattern 条件有 Simple Conditions 及 Combining Conditions 等类型。

  • 简单条件:Simple Condition 继承于 Iterative Condition 类,其主要根据事件中的字段信息进行判断,决定是否接受该事件。
// 把通话成功的事件挑选出来
start.where(_.getCallType == "success")
  • 组合条件:组合条件是将简单条件进行合并,通常情况下也可以使用 where 方法进行条件的组合,默认每个条件通过 AND 逻辑相连。如果需要使用 OR 逻辑,直接使用 or 方法连接条件即可。
// 把通话成功,或者通话时长大于10秒的事件挑选出来
val start = Pattern.begin[StationLog]("start_pattern")
.where(_.callType=="success")
.or(_.duration>10)
  • 终止条件:如果程序中使用了 oneOrMore 或者 oneOrMore().optional()方法,则必须指定终止条件,否则模式中的规则会一直循环下去,如下终止条件通过 until()方法指定。
pattern.oneOrMore.until(_.callOut.startsWith("186"))

模式序列

将相互独立的模式进行组合然后形成模式序列。模式序列基本的编写方式和独立模式一致,各个模式之间通过邻近条件进行连接即可,其中有严格邻近、宽松邻近、非确定宽松邻近三种邻近连接条件。

  • 严格邻近:严格邻近条件中,需要所有的事件都按照顺序满足模式条件,不允许忽略任意不满足的模式。
val strict: Pattern[Event] = start.next("middle").where(...)
  • 宽松邻近:在宽松邻近条件下,会忽略没有成功匹配模式条件,并不会像严格邻近要求得那么高,可以简单理解为 OR 的逻辑关系。
val relaxed: Pattern[Event, _] = start.followedBy("middle").where(...)
  • 非确定宽松邻近:和宽松邻近条件相比,非确定宽松邻近条件指在模式匹配过程中可以忽略已经匹配的条件。
val nonDetermin: Pattern[Event, _] = start.followedByAny("middle").where(...)
  • 除以上模式序列外,还可以定义“不希望出现某种近邻关系”:
.notNext() —— 不想让某个事件严格紧邻前一个事件发生
.notFollowedBy() —— 不想让某个事件在两个事件之间发生

注意:
1、所有模式序列必须以 .begin() 开始
2、模式序列不能以 .notFollowedBy() 结束
3、“not” 类型的模式不能被 optional 所修饰
4、此外,还可以为模式指定时间约束,用来要求在多长时间内匹配有效

//指定模式在10秒内有效
pattern.within(Time.seconds(10));

2、模式检测

调用 CEP.pattern(),给定输入流和模式,就能得到一个 PatternStream

//cep 做模式检测
val patternStream = CEP.pattern[EventLog](dataStream.keyBy(_.id),pattern)

选择结果

得到 PatternStream 类型的数据集后,接下来数据获取都基于 PatternStream 进行。该数据集中包含了所有的匹配事件。目前在 FlinkCEP 中提供 select 和 flatSelect 两种方法从 PatternStream 提取事件结果事件。

1.通过 Select Funciton 抽取正常事件

可以通过在 PatternStream 的 Select 方法中传入自定义 Select Funciton 完成对匹配事件的转换与输出。其中 Select Funciton 的输入参数为 Map[String, Iterable[IN]],Map中的 key 为模式序列中的 Pattern 名称,Value 为对应 Pattern 所接受的事件集合,格式为输入事件的数据类型。

def selectFunction(pattern : Map[String, Iterable[IN]]): OUT = {
//获取pattern中的startEvent
val startEvent = pattern.get("start_pattern").get.next
100
//获取Pattern中middleEvent
val middleEvent = pattern.get("middle").get.next
//返回结果
OUT(startEvent, middleEvent)
}

2.通过 Flat Select Funciton 抽取正常事件

Flat Select Funciton 和 Select Function 相似,不过 Flat Select Funciton 在每次调用可以返回任意数量的结果。因为 Flat Select Funciton 使用 Collector 作为返回结果的容器,可以将需要输出的事件都放置在 Collector 中返回。

def flatSelectFn(pattern : Map[String, Iterable[IN]], collector :
Collector[OUT]) = {
//获取pattern中startEvent
val startEvent = pattern.get("start_pattern").get.next
//获取Pattern中middleEvent
val middleEvent = pattern.get("middle").get.next
//并根据startEvent的Value数量进行返回
for (i <- 0 to startEvent.getValue) {
collector.collect(OUT(startEvent, middleEvent))
}}

3.通过 Select Funciton 抽取超时事件

如果模式中有 within(time),那么就很有可能有超时的数据存在,通过 PatternStream.Select 方法分别获取超时事件和正常事件。首先需要创建 OutputTag 来标记超时事件,然后在 PatternStream.select 方法中使用 OutputTag,就可以将超时事件从 PatternStream中抽取出来。

// 通过CEP.pattern方法创建PatternStream
val patternStream: PatternStream[Event] = CEP.pattern(input, pattern)
//创建OutputTag,并命名为timeout-output
val timeoutTag = OutputTag[String]("timeout-output")
//调用PatternStream select()并指定timeoutTag
val result: SingleOutputStreamOperator[NormalEvent] =
patternStream.select(timeoutTag){
//超时事件获取
(pattern: Map[String, Iterable[Event]], timestamp: Long) =>
TimeoutEvent()//返回异常事件
} {
//正常事件获取
pattern: Map[String, Iterable[Event]] =>
101
NormalEvent()//返回正常事件
}
//调用getSideOutput方法,并指定timeoutTag将超时事件输出
val timeoutResult: DataStream[TimeoutEvent] =
result.getSideOutput(timeoutTag)

案例

需求:从一堆的登录日志中,匹配一个恶意登录的模式(如果一个用户连续失败三次,则是恶意登录),从而到哪些用户名是恶意登录。

package com.learning.flink

import java.util

import org.apache.flink.cep.PatternSelectFunction
import org.apache.flink.cep.scala.{CEP, PatternStream}
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time

/**
  * 登录告警系统
  * 从一堆的登录日志中,匹配一个恶意登录的模式(如果一个用户连续失败三次,则是
  * 恶意登录),从而找到哪些用户名是恶意 登录
  */
case class EventLog(id:Long,userName:String,eventType:String,eventTime:Long)

object FlinkCEP {
  def main(args: Array[String]): Unit = {
    val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    streamEnv.setParallelism(1)
    import org.apache.flink.streaming.api.scala._
    streamEnv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    val stream: DataStream[EventLog] = streamEnv.fromCollection(List(
      new EventLog(1, "张三", "fail", 1574840003),
      new EventLog(1, "张三", "fail", 1574840004),
      new EventLog(1, "张三", "fail", 1574840005),
      new EventLog(2, "李四", "fail", 1574840006),
      new EventLog(2, "李四", "sucess", 1574840007),
      new EventLog(1, "张三", "fail", 1574840008)
    )).assignAscendingTimestamps(_.eventTime * 1000)
    //定义模式
    val pattern: Pattern[EventLog, EventLog] =
      Pattern.begin[EventLog]("begin").where(_.eventType.equals("fail"))
        .next("next1").where(_.eventType.equals("fail"))
        .next("next2").where(_.eventType.equals("fail"))
        .within(Time.seconds(10))
    //cep 做模式检测
    val patternStream: PatternStream[EventLog] =
      CEP.pattern[EventLog](stream.keyBy(_.id),pattern)
    //第三步: 输出alert
    val result: DataStream[String] = patternStream.select(new PatternSelectFunction[EventLog, String] {
      override def select(map: util.Map[String, util.List[EventLog]]): String = {
        val iter: util.Iterator[String] = map.keySet().iterator()
        val e1: EventLog = map.get(iter.next()).iterator().next()
        val e2: EventLog = map.get(iter.next()).iterator().next()
        val e3: EventLog = map.get(iter.next()).iterator().next()
        "id:" + e1.id + " 用户名:" + e1.userName + "登录的时间:" + e1.eventTime + ":" + e2.eventTime + ":" + e3.eventTime
      }
    })
    result.print(" main ")
    streamEnv.execute()
  }
}

结果:

 main > id:1 用户名:张三登录的时间:1574840003:1574840004:1574840005
 main > id:1 用户名:张三登录的时间:1574840004:1574840005:1574840008
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值