当一个应用被提交时,Dispatcher分发器就会启动并将应用移交给一个JobManager。
JobManager控制一个应用程序执行的主进程,每个应用程序都会被一个不同的JobManager所控制;JobManager先接收到要执行的应用程序(包括作业图JobGraph、逻辑数据流图Logical dataflow gragh、打包的所有的类、库和其他资源的jar包;JobManager会将JobGraph转换为一个物理层面的数据流图(执行图:ExecutionGragh),包含了所有可以并发执行的任务; JobManager会向资源管理器(ResourceManager)请求执行任务必要的资源,即TaskManager上的插槽(Slot),一旦获取到足够的资源,就会将执行图分发到真正运行它们的TaskManager上;在运行过程中,JobManager会负责所有需要中央协调的操作,比如检查点(checkpoints)的协调。
ResourceManager主要负责管理任务管理器(TaskManager)的插槽(slot),Slot时Flink定义的处理资源单元;ResourceManager将有空闲插槽的TaskManager分配给JobManager。如果ResourceManager没有足够的插槽来满足JobManager的请求,它可以向资源提供平台发起会话,以提供启动TaskManager进程的容器。
TaskManager一般在Flink的工作进程中会有多个,每个TaskManager都包含一定数量的插槽slots,插槽的数量限制了TaskManager能够执行的任务数量。TaskManager启动之后,TaskManager会向ResoureManger注册它的插槽,收到ResourceManger的指令后,TaskManager就会将一个或多个插槽提供个JobManager调用,JobManager就可以向插槽分配任务(tasks)来执行。在执行过程中,一个TaskManager可以跟其他运行同一应用程序的TaskManager交换数据。
Tumbling window 固定相同间隔分配窗口,每个窗口之间没有重叠。
//tumbling time windows(翻滚时间窗口)
data.keyBy(1)
.timeWindow(Time.minutes(1)) //tumbling time window 每分钟统计一次数量和
.sum(1);
--------------------------------------------------------------------------------------------------
Sliding Windows 固定相同间隔分配窗口,只不过每个窗口之间有重叠。窗口重叠的部分如果比窗口小,窗口将会有多个重叠,即一个元素可能被分配到多个窗口里去。
data.keyBy(1)
.timeWindow(Time.minutes(1), Time.seconds(30)) //sliding time window 每隔 30s 统计过去一分钟的数量和
.sum(1);
--------------------------------------------------------------------------------------------------
Session Windows: 主要是根据活动的事件进行窗口化,他们通常不重叠,也没有一个固定的开始和结束时间。一个session window关闭通常是由于一段时间没有收到元素。在这种用户交互事件流中,我们首先想到的是将事件聚合到会话窗口中(一段用户持续活跃的周期),由非活跃的间隙分隔开。
// 静态间隔时间
WindowedStream Rates = rates
.keyBy(MovieRate::getUserId)
.window(EventTimeSessionWindows.withGap(Time.milliseconds(10)));
// 动态时间
WindowedStream Rates = rates
.keyBy(MovieRate::getUserId)
.window(EventTimeSessionWindows.withDynamicGap(()));
--------------------------------------------------------------------------------------------------
Global window:同keyed的元素分配到一个窗口里
WindowedStream Rates = rates
.keyBy(MovieRate::getUserId)
.window(GlobalWindows.create());
ValueState
,ListState,MapState
ValueState
: This keeps a value that can be updated and retrieved (scoped to key of the input element as mentioned above, so there will possibly be one value for each key that the operation sees). The value can be set using update(T)
and retrieved using T value()
.
ListState
: This keeps a list of elements. You can append elements and retrieve an Iterable
over all currently stored elements. Elements are added using add(T)
or addAll(List
, the Iterable can be retrieved using Iterable
. You can also override the existing list with update(List
ReducingState
: This keeps a single value that represents the aggregation of all values added to the state. The interface is similar to ListState
but elements added using add(T)
are reduced to an aggregate using a specified ReduceFunction
.
AggregatingState
: This keeps a single value that represents the aggregation of all values added to the state. Contrary to ReducingState
, the aggregate type may be different from the type of elements that are added to the state. The interface is the same as for ListState
but elements added using add(IN)
are aggregated using a specified AggregateFunction
.
FoldingState
: This keeps a single value that represents the aggregation of all values added to the state. Contrary to ReducingState
, the aggregate type may be different from the type of elements that are added to the state. The interface is similar to ListState
but elements added using add(T)
are folded into an aggregate using a specified FoldFunction
.
MapState
: This keeps a list of mappings. You can put key-value pairs into the state and retrieve an Iterable
over all currently stored mappings. Mappings are added using put(UK, UV)
or putAll(Map
. The value associated with a user key can be retrieved using get(UK)
. The iterable views for mappings, keys and values can be retrieved using entries()
, keys()
and values()
respectively. You can also use isEmpty()
to check whether this map contains any key-value mappings.
flink的state可以设置过期时间,类似于redis
import org.apache.flink.api.common.state.StateTtlConfig;
StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.seconds(1))
.disableCleanupInBackground()
.build();
Flink的窗口处理流式数据虽然提供了基础EventTime的WaterMark机制,但是只能在一定程度上解决数据乱序问题。而某些极端情况下数据延迟会非常严重,即便通过WaterMark机制也无法等到数据全部进入窗口再进行处理。默认情况下,Flink会将这些严重迟到的数据丢弃掉;如果用户希望即使数据延迟到达,也能够按照流程处理并输出结果,此时可以借助Allowed Lateness机制来对迟到的数据进行额外的处理。
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.SlidingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
object demo1 {
def main(args: Array[String]): Unit = {
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime )
val inputStream: DataStream[String] = env.socketTextStream("hadoop102",7777)
val outputTag = new OutputTag[SensorReading]("side")
val dataStream = inputStream
.map(data => {
val dataArray = data.split(",")
SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(2)) {
override def extractTimestamp(element: SensorReading): Long = {
element.timestamp*1000 //我的测试时间戳是s,flink要求ms
}
})
val minStream: DataStream[SensorReading] = dataStream.keyBy(_.id)
// .window( SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(2)))
.timeWindow(Time.seconds(10))
.allowedLateness(Time.seconds(4))
.sideOutputLateData(outputTag)
.minBy("temperature")
dataStream.print("data")
minStream.print("min")
minStream.getSideOutput(outputTag).print("slide")
env.execute("demo1")
}
}
case class SensorReading(id: String, timestamp: Long, temperature: Double)
注意参数
1、窗口开窗为10s ,此次采用滚动窗口比较简单点
2、watermark为2s
3、允许延迟为4s
注意事项
1、如果用我的代码进行测试,不要修改测试数据第一条,因为涉及到计算窗口的start
测试数据
sensor_1, 1547718120,20
sensor_1, 1547718130,10
sensor_1, 1547718131,9
sensor_1, 1547718132,8
sensor_1, 1547718120,9
sensor_1, 1547718135,5
sensor_1, 1547718120,9
sensor_1, 1547718136,4
sensor_1, 1547718120,9
打印结果
data> SensorReading(sensor_1,1547718120,20.0)
data> SensorReading(sensor_1,1547718130,10.0)
data> SensorReading(sensor_1,1547718131,9.0)
data> SensorReading(sensor_1,1547718132,8.0)
min> SensorReading(sensor_1,1547718120,20.0)
data> SensorReading(sensor_1,1547718120,9.0)
min> SensorReading(sensor_1,1547718120,9.0)
data> SensorReading(sensor_1,1547718135,5.0)
data> SensorReading(sensor_1,1547718120,9.0)
min> SensorReading(sensor_1,1547718120,9.0)
data> SensorReading(sensor_1,1547718136,4.0)
data> SensorReading(sensor_1,1547718120,9.0)
slide> SensorReading(sensor_1,1547718120,9.0)
说明
1、经过计算窗口的开始时间是1547718120,所以第一个窗口是【20-30),
2、第一个窗口关闭的时间是20+10+2=32,所以当输入32这条数据的时候【20-30)的窗口关闭,此时窗口内的数据只有20,所以算出温度最小值为20
3、当输入 SensorReading(sensor_1,1547718120,9.0)这条数据的时候,allowlateness起作用,认为这条数据也是延迟数据,对原先算出的最小值20进行修正,最后算出min=9.0
4、此时需要计算一个最多延迟时间20+10+2+4=36,所以输入35的时候,这条数据,会进入到第二个窗口,同时第一个窗口还没有彻底关闭,所以再次输入 SensorReading(sensor_1,1547718120,9.0),仍然会进入到【20-30)的窗口,并在此计算最小值
5、输入SensorReading(sensor_1,1547718136,4.0),窗口彻底关闭,再次输入 SensorReading(sensor_1,1547718120,9.0),不再对第一个窗口min进行修正,直接把数据放到测输入流,以后所有的【20-30)的数据在输入都会全部放到侧输出流
总结
1、窗口window 的作用是为了周期性的获取数据
2、watermark的作用是防止数据出现乱序(经常),事件时间内获取不到指定的全部数据,而做的一种保险方法,
3、allowLateNess,是将窗口关闭时间再延迟一段时间,
思考?这里的allowLateNess 感觉就好像window变大了,那么为什么不直接把window设置大一点呢?或者把watermark加大点
业务需要,比如我业务需要统计每个小时内的数据,那么开窗一定是1h,但是数据乱序可能会达到几分钟,一般来说水印设置的都比较小(为什么呢?暂时不知道),所以提出了延迟时间这个概念
4、sideOutPut是最后兜底操作,所有过期延迟数据,指定窗口已经彻底关闭了,就会把数据放到侧输出流
在流处理中保证高性能同时又要保证容错是比较困难的。在批处理中,当作业失败时,可以容易地重新运行作业的失败部分来重新计算丢失的结果。这在批处理中是可行的,因为文件可以从头到尾重放。但是在流处理中却不能这样处理。数据流是无穷无尽的,没有开始点和结束点。带有缓冲的数据流可以进行重放一小段数据,但从最开始重放数据流是不切实际的(流处理作业可能已经运行了数月)。此外,与仅具有输入和输出的批处理作业相比,流计算是有状态的。这意味着除了输出之外,系统还需要备份和恢复算子状态。由于这个问题比较复杂,因此在开源生态系统中有许多容错方法去尝试解决这个问题。
用于容错机制对整个框架的架构有比较深的影响。很难将不同的容错机制进行插件化来整合到现有框架中。因此,在我们选择一个流处理框架时,容错机制也非常重要。
下面我们去了解一下流处理架构的几种容错方法,从记录确认
到微批处理
,事务更新
和分布式快照
。我们将从以下几个维度讨论不同方法的优缺点,最终选出融合不同方法优点适合流处理程序的融合方法:
上面我们忽略了一个共同特征,即失败后的快速恢复,不是因为它不重要,而是因为(1)所有介绍的系统都能够基于完全并行进行恢复,以及(2)在有状态的应用程序中,状态恢复的瓶颈通常在于存储而不是计算框架。
下面这个图非常清晰的解释了。
9.Flink自定义sink和source有没有写过,遇到了什么问题?
10.Flink自定udf函数有没有写过,解决的什么问题?
项目
1.你们项目中有没有遇到过背压?如何解决的?
2.你们项目中有没有遇到数据倾斜?如何解决的?
3.你们项目中有没有遇到状态异常需要人工修改?如何解决的?
4.你们项目中有没有遇到离线数据历史数据需要迁移到实时流中,比如历史视频的播放量,想要衔接到实时流中进行累加?如何解决的?
5.你们项目中有没有遇到手动维护kafka的offset,如何获取kafka的offset?
6.你们项目中有没有遇到checkpoint的oom现象,rocksDB有点和不足,checkpoint和savepoint的区别是什么?
7.你们项目中有没有遇到异步io读写的场景?
8.你们项目中有没有使用过广播的场景?
9.你们项目中有没有使用实时去重复,实时topN的场景,如何做的?
面试
1.梳理项目背景,你做的什么项目,数据量多少,这个项目应用场景。
2.每天多少条数据,数据量多大容量(多少TB)每秒钟处理多少条数据,你在项目中遇到了哪些问题,你是如何解决的?
3.项目中你用到了什么技术,这个技术有什么优点和不足,你要思考,为什么选这个技术,其他技术为什么可以?这个你要思考。
4.你的任务什么时间调度,有没有相应的监控,数据异常了有没有报警
5.思考好项目组分工,如何跟前端交互的,数据来源+加工+呈现,这个流程梳理清楚