package api
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{EventTimeSessionWindows, SlidingProcessingTimeWindows, TumblingEventTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
/**
*
* @PROJECT_NAME: Flink
* @PACKAGE_NAME: api
* @author: 赵嘉盟-HONOR
* @data: 2025-05-14 1:46
* @DESCRIPTION
*
*/
object Window {
def main(args: Array[String]): Unit = {
val env=StreamExecutionEnvironment.getExecutionEnvironment
// val inputPath="H:\\Scala程序\\Flink\\src\\main\\resources\\source.txt"
// val stream=env.readTextFile(inputPath)
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) //添加事件时间语义,不添加默认Processing time(操作时间)
env.getConfig.setAutoWatermarkInterval(500) //设置水平线生成周期
val inputStream=env.socketTextStream("localhost",7777)
//先转换样例类(简单转换操作)
val dataStream=inputStream
.map(data=>{
val arr=data.split(",")
SensorReading(arr(0),arr(1).toLong,arr(2).toDouble)
})
//.assignAscendingTimestamps(_.timestamp*1000l) //升序数据提取时间戳方法,直接使用事件时间戳
.assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(3)) { //最大乱序程度
override def extractTimestamp(t: SensorReading): Long = t.timestamp*1000l //提取时间戳
})
//每十五秒统计一下窗口内各个传感器温度的最小值,以及最新的时间戳
val resultStream=dataStream
.map( data=>(data.id,data.temperature,data.timestamp))
.keyBy(_._1)
//.window( TumblingEventTimeWindows.of(Time.days(1),Time.hours(-8))) //滚动时间窗口
//.window( SlidingProcessingTimeWindows.of(Time.seconds(15),Time.seconds(10))) //滑动时间窗口
//.window( EventTimeSessionWindows.withGap(Time.seconds(10))) //绘画窗口
.timeWindow(Time.seconds(15)) //简写方法
.allowedLateness(Time.minutes(1)) //允许处理迟到数据
.sideOutputLateData(new OutputTag[(String, Double, Long)]("late")) //写进侧输出流
//.minBy(1)
.reduce(
(curRes,newData)=>(curRes._1,curRes._2.min(newData._2),newData._3))
resultStream.getSideOutput(new OutputTag[(String, Double, Long)]("late")).print("late") //获取测输出流
resultStream.print("resulta")
env.execute("window")
}
}
这段代码是一个使用 Apache Flink 进行流处理的示例,重点展示了如何基于事件时间(Event Time)处理数据流,并使用窗口操作进行聚合。以下是代码的详细解释和背景知识拓展。
代码解释
1. 环境设置
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.getConfig.setAutoWatermarkInterval(500)
StreamExecutionEnvironment.getExecutionEnvironment
:获取流处理执行环境。env.setParallelism(1)
:设置并行度为 1,方便调试。env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
:设置时间语义为事件时间(Event Time),即使用数据本身的时间戳。env.getConfig.setAutoWatermarkInterval(500)
:设置水位线(Watermark)生成周期为 500 毫秒。
2. 数据源
val inputStream = env.socketTextStream("localhost", 7777)
- 从本地主机的 7777 端口读取数据流。
3. 数据转换
val dataStream = inputStream
.map(data => {
val arr = data.split(",")
SensorReading(arr(0), arr(1).toLong, arr(2).toDouble)
})
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(3)) {
override def extractTimestamp(t: SensorReading): Long = t.timestamp * 1000L
})
map
:将每行数据转换为SensorReading
对象,包含id
、timestamp
和temperature
字段。assignTimestampsAndWatermarks
:为数据流分配时间戳和水位线。BoundedOutOfOrdernessTimestampExtractor
:允许数据乱序,最大乱序时间为 3 秒。extractTimestamp
:从SensorReading
对象中提取时间戳,并转换为毫秒。
4. 窗口操作
val resultStream = dataStream
.map(data => (data.id, data.temperature, data.timestamp))
.keyBy(_._1)
.timeWindow(Time.seconds(15))
.allowedLateness(Time.minutes(1))
.sideOutputLateData(new OutputTag[(String, Double, Long)]("late"))
.reduce((curRes, newData) => (curRes._1, curRes._2.min(newData._2), newData._3))
map
:将SensorReading
对象转换为三元组(id, temperature, timestamp)
。keyBy
:按照id
字段对数据进行分组。timeWindow
:定义一个 15 秒的滚动窗口。allowedLateness
:允许处理迟到数据,最大延迟时间为 1 分钟。sideOutputLateData
:将迟到数据写入侧输出流。reduce
:对窗口内的数据进行聚合,保留id
、最小温度和最新时间戳。
5. 侧输出流
resultStream.getSideOutput(new OutputTag[(String, Double, Long)]("late")).print("late")
- 获取侧输出流中的数据并打印。
6. 结果输出
resultStream.print("result")
env.execute("window")
- 打印主数据流的结果。
env.execute("window")
:启动流处理任务。
背景知识拓展
1. 时间语义
- 事件时间(Event Time):数据本身的时间戳,通常用于处理乱序数据。
- 处理时间(Processing Time):数据被处理时的时间,简单但无法处理乱序数据。
- 摄入时间(Ingestion Time):数据进入 Flink 系统的时间。
2. 水位线(Watermark)
- 作用:用于处理乱序数据,表示事件时间的进度。
- 生成方式:可以通过
BoundedOutOfOrdernessTimestampExtractor
等工具生成。 - 乱序容忍:允许数据在一定时间内乱序,超过该时间的数据会被丢弃或写入侧输出流。
3. 窗口操作
- 滚动窗口(Tumbling Window):固定大小的窗口,窗口之间不重叠。
- 滑动窗口(Sliding Window):固定大小的窗口,窗口之间可以重叠。
- 会话窗口(Session Window):根据数据之间的间隔动态划分窗口。
4. 迟到数据处理
- 允许延迟(Allowed Lateness):允许窗口关闭后一段时间内处理迟到数据。
- 侧输出流(Side Output):将迟到数据写入侧输出流,以便后续处理。
5. Flink 的 API
- DataStream API:用于处理无界数据流。
- Table API & SQL:用于处理结构化数据。
- State & Checkpoint:用于管理状态和实现容错。
6. 流处理应用场景
- 实时监控:如传感器数据监控、日志分析。
- 实时推荐:如电商平台的实时推荐系统。
- 金融风控:如实时交易监控和欺诈检测。
进一步学习
- Flink 官方文档
https://flink.apache.org/docs/stable/
- 流处理与批处理:了解流处理与批处理的区别与应用场景。
- 分布式系统:学习分布式系统的基本概念与原理,如容错、一致性、分区等。
- Scala 编程
https://docs.scala-lang.org/
通过这段代码的学习,你可以掌握如何使用 Flink 处理基于事件时间的数据流,并了解窗口操作、水位线、迟到数据处理等核心概念。