Flink 学习总结

Flink 学习总结

Flink特点

  • 高吞吐、低延迟、高性能
  • 支持 Event Time
  • 支持有状态的计算
  • 高度灵活的窗口计算
  • 支持轻量级分布式快照Checkpoints

Flink和SparkStream对比♥♥♥

本质:

  • SparkStream 是以批处理技术为根本,将数据切成一个一个微小的批次从而实现流式处理(伪流处理)
  • Flink 是完全的流式处理,只要数据一来,就会马上对其进行处理

数据模型:

  • Spark采用RDD,SparkStreaming中的DStream也就是一组一组的小批次RDD
  • Flink 的基本数据模型是数据流,以及事件(Event)

运行架构:

  • Spark 是批计算,将 DAG 切分成多个 Stage,一个 Stage 做完才能计算下一个
  • Flink 是流执行模式,一个事件在一个节点处理完以后直接发往下一个节点 进行处理

Flink的两种安装模式

Standalone
Flink on yarn(开发重点)

Flink实现WordCount

1.获得一个执行环境;(Execution Environment)

val env = StreamExecutionEnvironment.getExecutionEnvironment

2.加载/创建初始数据;(Source)

val streamText = env.readTextFile("D:\\abc\\wordcount\\input")

3.指定转换这些数据;(Transformation)

val result = streamText.flatMap(_.split(" ")).map((_,1)).keyBy(0).sum(1)

4.指定放置计算结果的位置;(Sink)

result.writeAsText("D:\\abc\\wordcount\\output2")

5.触发程序执行(流处理)

env.execute()

Flink执行流程图

原理
流程图演变过程

DatastreamAPI 的 Source

kafkaSource

package org.example.source

import java.util.Properties

import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.kafka.clients.consumer.ConsumerConfig

object MyKafkaSource {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val props = new Properties()
    props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"master:9092")
    props.setProperty(ConsumerConfig.GROUP_ID_CONFIG,"jx")
    
    val inputStream = env.addSource(new FlinkKafkaConsumer[String]("test", new SimpleStringSchema(), props))
    inputStream.print()
    env.execute()
  }
}

自定义source

package org.example.source

import java.sql.{Connection, DriverManager, PreparedStatement}

import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala._
import org.example.bean.Teacher

/**
 * 自定义并发读取mysql数据
 */
object MyJDBCSource {
  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment

    env.addSource(new MyJDBCSourceFunc)
        .print()

    env.execute()

  }
}
class  MyJDBCSourceFunc extends RichParallelSourceFunction[Teacher] {
  var con:Connection = _
  var statement:PreparedStatement =_
  var flag:Boolean = true
  override def open(parameters: Configuration)={
    con = DriverManager.getConnection("jdbc:mysql://localhost:3306/school?characterEncoding =utf-8&serverTimezone=UTC",
      "root",
      "chenwei0302")
    statement = con.prepareStatement("select * from teacherinfo")
  }

  override def run(ctx: SourceFunction.SourceContext[Teacher]): Unit = {
    while (flag){
      Thread.sleep(5000)
      val rstSet = statement.executeQuery()
      while (rstSet.next()){
        val teacherId = rstSet.getInt(1)
        val teacherName = rstSet.getString(2)
        ctx.collect(Teacher(teacherId,teacherName))
      }
    }
  }

  override def cancel(): Unit = {
    flag=false
  }

  override def close(): Unit = {
    if(statement != null){ statement.close()}
    if(con!= null){con.close()}
  }
}

DatastreamAPI 的 Transform 算子

单流

  • Map
  • FlatMap
  • Fliter
  • KeyBy
  • Reduce
  • Aggregations 提供聚合算子。如求 max、min、sum

多流:

  • Union
  • Connect
  • CoMap、CoFlatMap
package org.example.transform
import org.apache.flink.streaming.api.functions.co.CoMapFunction
import org.apache.flink.streaming.api.scala._

/**
 * 多流转换算子
 */
object MuliDataStream {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val input1: DataStream[Int] = env.fromElements(1, 2, 3, 4, 5, 6)
    val input2 = env.fromElements(10, 60)
    val rst1: DataStream[Int] = input1.union(input2)


    val input3 = env.fromElements("a", "b", "c")
    val rst2: ConnectedStreams[Int, String] = input1.connect(input3)
    /*rst2.map(
      a =>println(a),
      b =>println(b)
    )*/
    rst2.map(new CoMapFunction[Int,String,String] {
      override def map1(in1: Int): String = {
        "tom0" + in1
      }
      override def map2(in2: String): String = {
        in2.toUpperCase()
      }
    }).print()

    env.execute()
  }

}

  • process 侧输出流
package org.example.transform
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

/**
 * 侧输出流实现分流
 * 奇数一个流偶数一个流
 */
object SideOutputStream {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val oddOutputTag = new OutputTag[Int]("odd")

    val rst: DataStream[Int] = env.fromElements(1, 2, 3, 4, 5, 6, 7)
      .process(new ProcessFunction[Int, Int] {
        override def processElement(i: Int, context: ProcessFunction[Int, Int]#Context, collector: Collector[Int]): Unit = {
          if (i % 2 == 0) {
            //输出到主流
            collector.collect(i)
          } else {
            //输出到侧输出流
            context.output(oddOutputTag, i)
          }
        }
      })
    rst.print("偶数:")
    rst.getSideOutput(oddOutputTag).print("奇数:")
    env.execute()
  }
}

UDF 函数
Flink 暴露了所有 udf 函数的接口(实现方式为接口或者抽象类)。例如 MapFunction, FilterFunction, ProcessFunction 等等
注意:
富函数可以获取运行环境的上下文,并拥 有一些生命周期方法,所以可以实现更复杂的功能。

分区算子

  • 随机分区 .shuffle
  • 循环分区 .rebalance
  • 调节分区 .rescale
  • 广播变量 .broadcast()
  • 自定义分区

DatastreamAPI 的 Sink

KafkaSink

package org.example.sink
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer
object MyKafkaSink {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val rst = env.readTextFile("D:\\Note\\Projects\\02\\Flink\\cha01\\file\\wc.txt")
      .flatMap(_.split(" "))
    
    rst.addSink(new FlinkKafkaProducer[String]("master:9092","test",new SimpleStringSchema()))

    env.execute()
  }
}

自定义Sink

package org.example.sink
import java.sql.{Connection, DriverManager, PreparedStatement}

import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.{RichSinkFunction, SinkFunction}
import org.apache.flink.streaming.api.scala._
import org.example.bean.Teacher
object MyJDBCSink {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val dataStream = env.readTextFile("D:\\Note\\Projects\\02\\Flink\\cha01\\file\\worker1.txt")
      .map(line => {
        val ps = line.split(",")
        Teacher(ps(0).toInt, ps(1))
      })

    dataStream.addSink(new MyJDBCSinkFunc)
  }
}
class MyJDBCSinkFunc extends  RichSinkFunction[Teacher]{
  var con:Connection = _
  //检查数据库有没有,没有插入,有就更新
  var updateStatement:PreparedStatement =_
  var insertStatement:PreparedStatement =_
  override def open(parameters: Configuration): Unit = {
    con = DriverManager.getConnection("jdbc:mysql://localhost:3306/school?characterEncoding =utf-8&serverTimezone=UTC",
      "root",
      "chenwei0302")
    updateStatement = con.prepareStatement("update teacherinfo set teacherName =? where teacherId=?")
    insertStatement = con.prepareStatement("insert into teacherinfo values (?,?)")

  }

  override def invoke(value: Teacher, context: SinkFunction.Context): Unit = {
    updateStatement.setInt(2,value.teacherId)
    updateStatement.setString(1,value.teacherName)
    updateStatement.execute()
    if(updateStatement.getUpdateCount == 0){
      insertStatement.setInt(1,value.teacherId)
      insertStatement.setString(2,value.teacherName)
      insertStatement.execute()
    }

    
  }

  override def close(): Unit = {
    if (updateStatement != null) updateStatement.close()
    if(insertStatement != null) insertStatement.close()
    if(con!= null) insertStatement.close()
  }
}

时间语义

事件时间:EventTime ♥♥♥♥重点
接入时间:IngestionTime 一般不用
处理时间:ProcessTime ♥♥♥

Window

在这里插入图片描述
EventTime

package org.example.window

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{EventTimeSessionWindows, SlidingEventTimeWindows, TumblingEventTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.example.bean.TrainAlarm

object WindowEventTime {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val inputStream: KeyedStream[TrainAlarm, String] = env.socketTextStream("master", 666)
      .map(line => {
        val ps = line.split(",")
        TrainAlarm(ps(0), ps(1).toLong, ps(2).toDouble)
      })
      //如果读取到的数据是有序并且升序的,那么就用assignAscendingTimestamps(使用的是毫秒数时间戳13位) 指定eventTime
      .assignAscendingTimestamps(_.ts * 1000L)
      .keyBy(_.id)

      //滚动 EventTime [200-205)
      inputStream .window(TumblingEventTimeWindows.of(Time.seconds(5)))
      .maxBy("temp")

      //滑动 EventTime
      inputStream .window(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(2)))
        .maxBy("temp")
      //会话 EventTime
    inputStream .window(EventTimeSessionWindows.withGap(Time.seconds(5)))
      .maxBy("temp")
      .print()


    env.execute()
  }
}

ProcessTime

package org.example.window

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{ProcessingTimeSessionWindows, SlidingProcessingTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time

/**
 *
 */
object WindowProcessTime {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val keyedStream: KeyedStream[(String, Int), String] = env.socketTextStream("master", 1314)
      .flatMap(_.split(" "))
      .map((_, 1))
      .keyBy(_._1)
    //滚动 ProcessingTime  传入的是滚动窗口的大小
    keyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      .sum(1)
    //滑动 ProcessingTime 传入的第一个是窗口大小 第二个是滑动补偿  最好整数倍关系
    keyedStream.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(2)))
        .sum(1)
    //会话 ProcessingTime
    keyedStream.window(ProcessingTimeSessionWindows.withGap(Time.seconds(5)))
      .sum(1)
  }
}


Watermark + EvenTime 解决数据乱序问题 ♥♥♥

Watermark = maxEvenTime - 延时时间
只要是windowEnd < Watermark 就会触发

package org.example.window
import java.time.Duration

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{EventTimeSessionWindows, SlidingEventTimeWindows, TumblingEventTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.example.bean.TrainAlarm
/**
 * 设定eventTime 和 watertime 处理乱序时间
 */
object AssignEventTimeAndWm {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val inputStream= env.socketTextStream("master", 666)
      .map(line => {
        val ps = line.split(",")
        TrainAlarm(ps(0), ps(1).toLong, ps(2).toDouble)
      })
      .assignTimestampsAndWatermarks(
      //Duration 设置延时时长 watermark = 当前已经到达的最大eventTime - 延时时长
      //只要比watermark小的窗口就可以触发
      WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))
        .withTimestampAssigner(new SerializableTimestampAssigner[TrainAlarm] {
          //设置eventTime是哪个字段
          override def extractTimestamp(element: TrainAlarm, l: Long): Long = {
            element.ts*1000L
          }
        })
    )

    inputStream.keyBy(_.id)
      .window(TumblingEventTimeWindows.of(Time.seconds(5)))
      .max("temp")
      .print()

     env.execute()
  }
}

allowlastness 解决迟到数据 ♥♥♥

原理:窗口触发了,但是没有关,在设定时间范围内只要数据来了还可以处理
案例

状态 state

  • 算子状态 算子状态的作用范围限定为算子任务,由同一并行任务所处理的所有数据都 可以访问到相同的状态,常用于 Source,需要实现 CheckpointedFunction 或者 ListCheckpointed 接口。
  • 键控状态 ♥♥
    值状态(Value state):将状态表示为单个值。
    列表状态(List state):将状态表示为一组数据的列表。
    映射状态(Map state):将状态表示为一组 Key-Value 对。
    聚合状态(Reducing state&Aggregate state):将状态表示为一个用于聚合的操 作。将一个新到的值直接带入进去做聚合操作。
    状态编程一般结合富函数(RichFunction)使用。然后通过上下文定义状态。
azy val lastTemp:ValueState[Double] = getRuntimeContext.getState(new ValueStateDescriptor[Double]("lastTemp",classOf[Double]))

案例:


package org.example.state

import org.apache.flink.api.common.state.ValueStateDescriptor
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
import org.example.bean.TrainAlarm

/**
 * 10 s 内温度连续上升就报警
 * 定义三个状态:温度状态、时间状态、个数状态
 * 如果是第一条数据,更新温度状态值、注册10s后触发的定时器并更行时间状态,个数状态设置为1
 * 如果不是第一条数据,
 *    如果温度比温度状态的值大,更新温度状态值,个数状态设置 +1
 *    如果温度比温度状态的值小,删除定时器(从时间状态中取时间)个数状态设置为1,重新注册定时器,更新温度状态值
 */
object TrainTempAlarmWithState2 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    val rst = env.socketTextStream("master", 666)
      .map(line => {
        val ps = line.split(",")
        TrainAlarm(ps(0), ps(1).toLong, ps(2).toDouble)
      }).assignAscendingTimestamps(_.ts*1000L)
      .keyBy(_.id)
      .process(new TempRiseWithTime())
      .print()
    env.execute()
  }
}
class TempRiseWithTime extends KeyedProcessFunction[String,TrainAlarm,String]{
  //定义三个状态:温度状态、时间状态、个数状态
  lazy val tempState = getRuntimeContext.getState[Double](new ValueStateDescriptor[Double]("tempstate",classOf[Double]))
  lazy val timeState = getRuntimeContext.getState[Long](new ValueStateDescriptor[Long]("timestate",classOf[Long]))
  lazy val countState = getRuntimeContext.getState[Int](new ValueStateDescriptor[Int]("countstate",classOf[Int]))
  override def processElement(value: TrainAlarm, ctx: KeyedProcessFunction[String, TrainAlarm, String]#Context, out: Collector[String]): Unit = {
    if (tempState.value()==0 || timeState.value() ==0){
      tempState.update(value.temp)
      ctx.timerService().registerEventTimeTimer(value.ts*1000L+10000L)
      timeState.update(value.ts*1000L+10000L)
      countState.update(1)
    }else{
      if (value.temp>= tempState.value()){
        tempState.update(value.temp)
        countState.update(countState.value()+1)
      }else{
        ctx.timerService().deleteEventTimeTimer(timeState.value())
        countState.update(1)
        ctx.timerService().registerEventTimeTimer(value.ts*1000L+10000L)
        timeState.update(value.ts*1000L+10000L)
        tempState.update(value.temp)
      }
    }
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, TrainAlarm, String]#OnTimerContext, out: Collector[String]): Unit = {
    if(countState.value()>=2){
      out.collect(ctx.getCurrentKey + " is alarming")
      tempState.clear()
      timeState.clear()
      countState.clear()
    }
  }
}


TableAPI 和 FlinkSQL 的基本使用

Table API 和 SQL 集成在同一套 API 中。这套 API 的核心概念是 Table,用 作查询的输入和输出。
依赖导入

<dependency> 
	<groupId>org.apache.flink</groupId> 
	<artifactId>flink-table-api-scala-bridge_2.12</artifactId> 
	<version>${flink.version}</version> 
</dependency> 
<dependency> 
	<groupId>org.apache.flink</groupId> 
	<artifactId>flink-table-planner-blink_2.12</artifactId> 
	<version>${flink.version}</version> 
</dependency> 
<dependency> 
	<groupId>org.apache.flink</groupId> 
	<artifactId>flink-table-common</artifactId> 
	<version>${flink.version}</version> 
</dependency> 
<dependency> 
	<groupId>org.apache.flink</groupId> 
	<artifactId>flink-csv</artifactId> 
	<version>${flink.version}</version> 
</dependency>
	 <dependency> 
	 <groupId>org.apache.flink</groupId>
	 <artifactId>flink-json</artifactId> 
	 <version>${flink.version}</version> 
 </dependency>

程序结构
创建一个表环境

	val bsEnv = StreamExecutionEnvironment.getExecutionEnvironment
    bsEnv.setParallelism(1)
    val bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build()
    val bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings)

连接外部数据源,创建一个输入表

tableEnv.executeSql("CREATE TEMPORARY TABLE table1 ... WITH ( 'connector' = ... )")

连接外部数据输出,注册一个输出表

tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector' = ... )") 

TableAPI 实现查询操作
1.TableAPI:

 val table2 = tableEnv.from("table1").select(...) 

2.FlinkSQL:

val table3 = tableEnv.sqlQuery("SELECT ... FROM table1 ...")

val table3 = tableEnv.sqlQuery(“SELECT … FROM table1 …”)
将查询结果放入注册好的表中

val tableResult = table2.executeInsert("outputTable") tableResult...

TableAPI 和 FlinkSQL 的两种connect

DataStream 和Table的转换

DataStream 和Table的转换的实现

窗口计算

窗口计算的实现

广播状态

广播状态(Broadcast State)是 Operator State 的一种特殊类型。如果我们需要 将 配 置 、 规 则 等 低 吞 吐 事 件 流 广 播 到 下 游 所 有 Task 时 , 就 可 以 使 用 BroadcastState。
在这里插入图片描述
代码实现

状态一致性的保证

端到端的一致性

反压

消息处理速度 < 消息的发送速度,消息拥堵,系统运行不畅。

  • Flink1.5 之前是基于 TCP 的反压机制。以 WordCount 程序为例。有基于 TaskManager 和 TaskManager 之间的反压,也有 TaskManager 内部的反压。
    在这里插入图片描述
  • Flink1.5 之后 Flink 实现了 Credit-base 反压机制
    在这里插入图片描述

优化

优化

  • Flink 内存优化
  • 配置进程参数
  • 解决数据倾斜
  • Checkpoint 优化

CEP

Flink CEP(Complex Event Processing)是在 Flink 上层实现的复杂事件处理库。 将数据流通过一定的规则匹配,然后输出用户想得到的数据。FlinkCEP 是通过 equals()和 hashCode()方法来比较和匹配事件。
依赖导入

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

模式 Pattern

处理事件的规则,被叫做“模式”(Pattern)。 Flink CEP 提供了 Pattern API,用于对输入流数据进行复杂事件规则定义,用 来提取符合规则的事件序列。
程序结构:
定义一个 Pattern

val pattern = Pattern.begin[Event]("start")
.where(_.getId == 42) 
.next("middle")
.subtype(classOf[SubEvent])
.where(_.getVolume >= 10.0) 
.followedBy("end")
.where(_.getName == "end")

将事件流与 Pattern 进行匹配

val patternStream = CEP.pattern(input, pattern)

得到处理结果

val result: DataStream[Alert] = patternStream.process( 
new PatternProcessFunction[Event, Alert]() {
 override def processMatch( `match`: util.Map[String, util.List[Event]], ctx: PatternProcessFunction.Context, out: Collector[Alert]): Unit = {
  out.collect(createAlertFrom(pattern))
   } 
 })

量词:
1.pattern.oneOrMore(),指定期望一个给定事件出现一次或者多次的模式。 2.pattern.times(#ofTimes),指定期望一个给定事件出现特定次数的模式。 3.pattern.times(#fromTimes, #toTimes),指定期望一个给定事件出现次数在一 个最小值和最大值之间。
4.使用 pattern.greedy()方法让循环模式变成贪心的。
5.使用 pattern.optional()方法让所有的模式变成可选的,不管是否是循环模 式。
条件:

  • 简单条件(Simple Condition)
  • 组合条件(Combining Condition)
  • 终止条件(Stop Condition)
  • 迭代条件(Iterative Condition)

连接关系:

  • 严格近邻(Strict Contiguity) .next()
  • 宽松近邻(Relaxed Contiguity) .followedBy()
  • 非确定性宽松近邻(Non-Deterministic Relaxed Contiguity) 进 一 步 放 宽 条 件 , 之 前 已 经 匹 配 过 的 事 件 也 可 以 再 次 使 用 , 由 .followedByAny() 指定。 例如对于模式”a followedByAny b”,事件序列 [a, c, b1, b2] 匹配为 {a, b1},{a, b2}。
    在这里插入图片描述
    除以上模式序列外,还可以定义“不希望出现某种近邻关系”:
    .notNext() —— 不想让某个事件严格紧邻前一个事件发生
    .notFollowedBy() —— 不想让某个事件在两个事件之间发生
    需要注意
    所有模式序列必须以 .begin() 开始
    模式序列不能以 .notFollowedBy() 结束
    “not” 类型的模式不能被 optional 所修饰

超时的部分匹配

当一个模式通过 within 关键字定义了检测窗口时间,部分事件序列可能因 为超过窗口长度而被丢弃;为了能够处理这些超时的部分匹配,select 和 flatSelect API 调用允许指定超时处理程序。超时处理程序会接收到目前为止由模式匹配到的所有事件,由一个 OutputTag 定义接收到的超时事件序列。
案例一:

package org.example.cep


import java.util

import org.apache.flink.cep.{PatternSelectFunction, PatternTimeoutFunction}
import org.apache.flink.cep.functions.adaptors.PatternTimeoutSelectAdapter
import org.apache.flink.cep.scala.CEP
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time

/**
 * @Author JiXiang
 * @Date 11:59
 * @Desc 用户下单以后,设置订单失效时间
 *       如果用户下单15分钟未支付,则输出监控信息
 *
 **/
object FlinkCEPDemo2 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    val orderEventStream = env.fromCollection(List(
      OrderEvent(1, "create", 1558430842),        //2019-05-21 17:27:22
      OrderEvent(2, "create", 1558430843),        //2019-05-21 17:27:23
      OrderEvent(2, "other", 1558430845),         //2019-05-21 17:27:25
      OrderEvent(2, "pay", 1558430850),           //2019-05-21 17:27:30
      OrderEvent(1, "pay", 1558431920)            //2019-05-21 17:45:20
    )).assignAscendingTimestamps(_.ts * 1000)


    val pattern = Pattern.begin[OrderEvent]("begin")
      .where(_.behavior == "create")
      .followedBy("next")
      .where(_.behavior == "pay")
      .within(Time.minutes(15))


    val patternStream = CEP.pattern(orderEventStream.keyBy(_.orderId),pattern)

    val lastTag = new OutputTag[OrderResult]("lastTag")

    val result = patternStream.select[OrderResult, OrderResult](lastTag,
      // 处理超时数据
      new PatternTimeoutFunction[OrderEvent, OrderResult]() {
        override def timeout(pattern: util.Map[String, util.List[OrderEvent]], timeoutTimestamp: Long): OrderResult = {
          val orderEvent = pattern.get("begin").iterator().next()
          OrderResult(orderEvent.orderId, orderEvent.behavior, "time out")
        }
      },
      // 处理正常匹配到的数据
      new PatternSelectFunction[OrderEvent, OrderResult] {
        override def select(pattern: util.Map[String, util.List[OrderEvent]]): OrderResult = {
          val orderResult = pattern.get("begin").iterator().next()
          OrderResult(orderResult.orderId, orderResult.behavior, "aa bb")
        }
      }
    )

    result.print()
    result.getSideOutput(lastTag).print()

    env.execute()

  }
}

case class OrderResult(orderId:Int,behavior:String,info:String)

case class OrderEvent(orderId:Int,behavior:String,ts:Long)

案例二:

package org.example.cep

import java.time.Duration
import java.util

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.cep.functions.PatternProcessFunction
import org.apache.flink.cep.scala.CEP
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
import org.example.bean.UserLogin

/**
 * @Author JiXiang
 * @Date 12:24
 * @Desc 5s内一个用户连续登录失败多次
 *
 **/
object FlinkCEPDemo3 {
  def main(args: Array[String]): Unit = {
    // 1、获取一个DataStream
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    val dataStream = env.readTextFile("file:///D:\\Note\\Projects\\02\\Flink\\cha01\\file\\LoginLog.csv")
      .map(line => {
        val ps = line.split(",")
        UserLogin(ps(0).toLong, ps(1), ps(2), ps(3).toLong)
      })
      .assignTimestampsAndWatermarks(
        WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(1))
          .withTimestampAssigner(new SerializableTimestampAssigner[UserLogin] {
            override def extractTimestamp(t: UserLogin, l: Long): Long = {
              t.ts * 1000L
            }
          })
      ).keyBy(_.uid)

    val pattern = Pattern.begin[UserLogin]("begin")
      .where(_.status == "fail")
      .next("next")
      .where(_.status == "fail")
      .times(2)
      .within(Time.seconds(5))

    CEP.pattern(dataStream, pattern)
      .process(
        new PatternProcessFunction[UserLogin, String] {
          override def processMatch(map: util.Map[String, util.List[UserLogin]], context: PatternProcessFunction.Context, collector: Collector[String]): Unit = {
            val first = map.getOrDefault("begin", null).iterator().next()
            collector.collect(first.toString)
          }
        }
      ).print()

    env.execute()
  }
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值