Flink 的复杂事件处理 CEP

一、什么是CEP?

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

二、CEP使用

1.配置依赖

    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-cep-scala_2.11</artifactId>
      <version>${flink.version}</version>
    </dependency>

2.事件定义

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

三、PatternAPI使用步骤

1.输入事件流的创建

2.Pattern 的定义

3.Pattern 应用在事件流上检测

4.选取结果

1) 模式定义

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

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

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

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

(1)设置循环次数
对于已经创建好的 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();

timesOrMore:通过 timesOrMore 方法可以指定触发固定次数以上,例如执行两次以上。

// 触发两次或者多次 
start.timesOrMore(2);
// 触发两次或者多次,尽可能重复执行
 start.timesOrMore(2).greedy();
// 不触发或者触发两次以上,尽可能重复执行 
start.timesOrMore(2).optional().greedy();

(2) 定义条件
每个模式都需要指定触发条件,作为事件进入到该模式是否接受的判断依据,当事件中 的数值满足了条件时,便进行下一步操作。在 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"))

(3) 模式序列
将相互独立的模式进行组合然后形成模式序列。模式序列基本的编写方式和独立模式一致,各个模式之间通过邻近条件进行连接即可,其中有严格邻近、宽松邻近、非确定宽松邻 近三种邻近连接条件。
严格邻近:严格邻近条件中,需要所有的事件都按照顺序满足模式条件,不允许忽略任 意不满足的模式。

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、此外,还可以为模式指定时间约束,用来要求在多长时间内匹配有效

2) 模式检测

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

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

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

四、案例实现

业务需求:从用户登录的日志中,匹配一个恶意登录的模式(如果一个用户连续失败三次(10秒内),则是恶意登录),从而找到哪些用户是恶意登录。
代码实现:
/**
  *
  * @param id 用户id
  * @param userName 用户名
  * @param `type` 登录类型:成功、失败
  * @param eventTime 登录时间 精确到秒
  */
case class LoginEvent(id: String, userName: String, `type`: String, eventTime: Long)
object CEPByLogin {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 开启ck
    env.enableCheckpointing(300 * 1000L )
    env.setStateBackend(new FsStateBackend("hdfs://hadoop001:9000/checkpoint/cp1"))
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
    env.getCheckpointConfig.setCheckpointTimeout(5000)
    env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
    env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION) // 终止job不删除ck

    // source
    val stream= env.socketTextStream("flink101", 8888)
      .map(line => {
        var arr = line.split(",")
        LoginEvent(arr(0).trim, arr(1).trim, arr(2).trim, arr(3).trim.toLong)
      }).assignAscendingTimestamps(_.eventTime * 1000) // 指定时间必须精确到毫秒

    // 模式定义Pattern
    val pattern = Pattern.begin[LoginEvent]("start")
      .where(_.`type`.equals("fail1"))
      .next("fail2")
      .where(_.`type`.equals("fail"))
      .next("fail3")
      .where(_.`type`.equals("fail"))
      .within(Time.seconds(10)) // 时间限制

    // 检测Pattern
    val patternStream = CEP.pattern(stream.keyBy(_.id), pattern)  // 根据用户id分组

    // 选择结果输出
    val result = patternStream.select(new PatternSelectFunction[LoginEvent, String] {
      override def select(map: util.Map[String, util.List[LoginEvent]]): String = {
        val keyIter = map.keySet().iterator()
        val events1 = map.get(keyIter.next()).iterator().next()
        val events2 = map.get(keyIter.next()).iterator().next()
        val events3 = map.get(keyIter.next()).iterator().next()

        "userNmae: " + events1.userName + "LoginTime: " + events1.eventTime + ":" + events2.eventTime + ":" + events3.eventTime
      }
    })

    result.print("打印结果")
    env.execute("CEPByLogin")
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值