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的转换
窗口计算
广播状态
广播状态(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()
}
}