flink学习笔记(7)-Flink中的事件时间处理
本文主要讲解Flink提供时间语义:Flink内部实现时间戳和水位线,用来支持事件时间语义的流式应用
01
—
时间戳
(1)Flink处理的所有记录都必须包含时间戳
(2)只要保证数据流记录的时间戳随着数据流的前进大致递增就可以,因为实际数据中可能存在乱序
(3)Flink内部采用8字节Long值对时间戳进行编码,并将它们以元数据的形式附加在记录中
(4)内置算子会精确到毫秒
(5)自定义算子可以精确到微妙
02
—
水位线
(1)作用:在事件时间应用中推断每个任务当前的事件时间,基于时间的算子会使用这个时间来触发计算并推动进度前进。
(2)Flink中水位线是利用一些包含Long值时间戳的特殊记录来实现的
(3)水位线基本属性:
(3.1) 必须单调递增
(3.2)和记录的时间存在联系。一个时间戳为T的水位线表示,接下来的时间一定大于T
(3.3)乱序程度,最大乱序时间差,作为延迟时间。
(4)存在问题:会有时间戳小于或等于水位线的记录,这个就使得数据完整性不能保证,此类记录叫做迟到记录(late record)。Flink提供不同机制,后面会讲
(5)水位线意义:
(5.1)运行应用控制结果的完整性和延迟
(5.2)无需等待过多记录就触发计算
(5.3)相反,非常“保守”的水位线会增加处理延迟,但同时结果的完整性也有所提升
03
—
水位线传播和事件时间
(1)Flink内部将水位线实现为特殊记录,可以通过算子任务进行接收和发送
(2)任务内部的时间服务会维护一些计时器,它们依靠接收到的水位线来激活
(3)当任务接收到水位线:
(3.1)基于水位线记录的时间戳更新内部事件时间时钟
(3.2)任务的时间服务会找到所有触发小于更新后事件时间的触发器,对于每个到期的计时器,调用回调函数,利用它来执行计算及发出记录
(3.3)任务根据更新后的事件时间将水位线发出
(4)任务在收到新的水位线后,如何发送水位线和更新内部时间时钟
(4.1)一个任务会为它的每个输入分区都维护一个分区水位线(partition wartermark)
(4.2)水位线更新:当收到某个分区水位线后,任务会以接收值和当前值比较,取较大更新水位线
(4.3)事件时间更新:随后任务会把事件时间时钟调整为水位线最小的那个值
(4.4)没有考虑分区是否来自不同输入流,导致所有输入记录必须基于事件时间时钟来处理。如果输入流事件时间没有对齐,那么该行为会导致一些问题
(4.5)依赖于所有分区都会持续提供自增的水位线。如果一个任务没有从全部输入以常规间隔接收新的水位线,就会导致时间相关算子的处理延迟或状态大小激增
(4.6)当算子两个输入流水位线差距很大:其事件时间受制于那个相对较慢的流,而较快的流记录或中间结果会在状态中缓存,直到事件时间到达允许处理它们的那个时间点
04
—
时间戳分配和水位线生成
(1)时间戳和水位线来源
(1.1)时间戳和水位线通常是通过数据流刚刚进入流处理应用的时候分配和生成的
(1.2)必须显式地分配时间戳和水位线:因为不同的应用会选择不同的时间戳,水位线依赖于时间戳和数据流本身的特征
(2)在数据源完成
(2.1)利用SourceFunction在应用读入数据流的时候分配时间戳和水位线
(2.2)源函数会发出一条记录流
(2.3)每个发出的记录都可以附加一个时间戳,水位线可以作为特殊记录在任意时刻发出
(2.4)如果源函数不再发出水位线,可以把自己声明成空闲Flink在后继算子计算水位线时候,会将空闲的源函数排除在外。从而解决水位线不向前推进
(3)周期分配器(periodic assigner)
(3.1)DataStream API 提供了AssignerWithPeriodicWatermark的用户自定义函数,它可以用来从每条记录提取时间戳,并周期性地响应获取当前水位线的查询请求
(3.2)提取出的时间会附加到各自的记录上,查询得到的水位线会注入到数据流中
(3.3)每条记录都要提取时间戳
(4)定时分配器(punctuated assigner)
(4.1)另一个支持从记录提取时间戳的用户自定义函数AssignerWithPunctuatedWatermarks),它可以根据特殊输入记录生成水位线的情况
(4.2)这个函数不会强制从每条记录提取时间戳
(4.3)用户自定义的时间戳分配函数通常都会尽可能地靠近数据源算子
05
—
总结
本文讲解了水位线单调递增,水位线后的时间戳都大于水位线。当任务接收到水位线后的操作。以及时间戳的分配时间,水位线的生成方式。
感谢阅读。
期待点赞、分享、关注!
人人都想改变世界,却没有人愿意改变自己。--列夫.托尔斯泰