赞
踩
这些组件是:JobManager、ResourceManager、TaskManager和Dispatcher。 (JVM)
作为主进程(masterprocess) , JobManager控制着单个应用程序的执行。换句话说,每个应用都由一个不同的JobManager掌控。(JobManager还要负责所有需要集中协调的操作,如创建检查点,建立检查点、保存点及状态恢复)
简单业务描述:JM从RM申请执行任务的必要资源(TM处理槽)),一旦JM收到了足够的TM处理槽,就会将ExcecutionGraph(执行任务图,JobGraph有JM转换为ExecutionGraph)中的任务分发给TM来执行
负责管理Flink的处理资源单元一一TaskManager处理槽。(针对不同的环境和资源提供者(如YARN、Mesos、Kubernetes或独立部署), Flink提供了不同的ResourceManager)
简单业务描述:当JM向RM申请执行资源时,RM会指示拥有的空闲TM处理槽将提供给JM。如果RM的处理槽数量无法满足JM请求时,则RM可以喝资源给提供者通信,让他们提供额外容器来启动TM进程,RM还负责终止空闲的TM以释放计算资源
TaskManager是Flink的工作进程(workerprocess)。通常在Flink搭建过程中要启动多个TaskManager。每个TaskManager提供一定数量的处理槽。处理槽的数目限制了一个TaskManager可执行的任务数
业务简单描述:TM向RM注册处理槽,当接收到RM的指示时,TM向JM提供一个或者多个处理槽,JM向处理槽分配执行任务。同一应用不同任务的TM之间进行数据交换
Dispatcher会跨多个作业运行
业务简单描述:它提供了一个REST接口来让我们提交需要执行的应用。一旦某个应用提交执行,Dispatcher会启动一个JobManager并将应用转交给它。
**REST接口代表:**Dispatcher集群的HTTP人口可以受到防火墙的保护。
**Dispatcher同时还会启动一个WebUI:**用来提供有关作业执行的信息。
某些应用提交执行的方式(我们会在“应用部署”一节讨论)可能用不到Dispatcher。
在运行过程中,应用的任务会持续进行数据交换。TaskManager负责将数据从发送任务传输至接收任务。它的网络模块在记录传输前会先将它们收集到缓冲区中。换言之,记录并非逐个发送的,而是在缓冲区中以批次形式发送。
作用:该技术是1:有效利用网络资源:2:实现高吞吐的基础。它的机制类似于网络以及磁盘I/O协议中的缓冲技术。
每个TaskManager都有一个用于收发数据的网络缓冲池(每个缓冲默认32KB大小)。
流式应用需要以流水线方式交换数据,因此每对TaskManager之间都要维护一个或多个永久的TCP连接来执行数据交换。在Shuffle(随机)连接模式下,每个发送端任务都需要向任意一个接收任务传输数据。对于每一个接收任务,TaskManager都要提供一个专用的网络缓冲区,用于接收其他任务发来的数据
**问题:**通过网络连接逐条发送记录不但低效,还会导致很多额外开销。若想充分利用网络连接带宽,就需要对数据进行缓冲。在流处理环境下,缓冲的一个明显缺点是会增加延迟,因为记录首先要收集到缓冲区中而不会立即发送。
**流量控制机制:**接收任务会给发送任务授予一定的信用值,其实就是保留一些用来接收它数据的网络缓冲。一旦发送端收到信用通知,就会在信用值所限定的范围内尽可能多地传输缓冲数据,并会附带上积压量(已经填满准备传输的网络缓冲数目)大小。接收端使用保留的缓冲来处理收到的数据,同时依据各发送端的积压量信息来计算所有相连的发送端在下一轮的信用优先级。
**补充:**由于发送端可以在接收端有足够资源时立即传输数据,所以基于信用值的流量控制可以有效降低延迟。
如图所示在 Flink 层面实现反压机制,就是每一次 ResultSubPartition 向 InputChannel 发送消息的时候都会发送一个 backlog size 告诉下游准备发送多少消息,下游就会去计算有多少的 Buffer 去接收消息,算完之后如果有充足的 Buffer 就会返还给上游一个 Credit 告知他可以发送消息(图上两个 ResultSubPartition 和 InputChannel 之间是虚线是因为最终还是要通过 Netty 和 Socket 去通信),下面我们看一个具体示例。
假设我们上下游的速度不匹配,上游发送速率为 2,下游接收速率为 1,可以看到图上在 ResultSubPartition 中累积了两条消息,10 和 11, backlog 就为 2,这时就会将发送的数据 <8,9> 和 backlog = 2 一同发送给下游。下游收到了之后就会去计算是否有 2 个 Buffer 去接收,可以看到 InputChannel 中已经不足了这时就会从 Local BufferPool 和 Network BufferPool 申请,好在这个时候 Buffer 还是可以申请到的。
过了一段时间后由于上游的发送速率要大于下游的接受速率,下游的 TaskManager 的 Buffer 已经到达了申请上限,这时候下游就会向上游返回 Credit = 0,ResultSubPartition 接收到之后就不会向 Netty 去传输数据,上游 TaskManager 的 Buffer 也很快耗尽,达到反压的效果,这样在 ResultSubPartition 层就能感知到反压,不用通过 Socket 和 Netty 一层层地向上反馈,降低了反压生效的延迟。同时也不会将 Socket 去阻塞,解决了由于一个 Task 反压导致 TaskManager 和 TaskManager 之间的 Socket 阻塞的问题。
Flink采用一种名为任务链接的优化技术来降低某些情况下的本地通信开销。任务链接的前提条件是,多个算子必须有相同的并行度且通过本地转发通道( local forward channel)相连。(流水线应用反而不希望用到任务链接)
图3-6展示了流水线如何在任务链接模式下执行。多个算子的函数被“融合”到同一个任务中,在同一个线程内执行。函数生成的记录只需通过简单的方法调用就可以分别发往各自的下游函数。因此函数之间的记录传输基本上不存在序列化及通信开销。
虽然任务链接可以有效地降低本地任务之间的通信开销,但有的流水线应用反而不希望用到它。举例而言,有时候我们需要对过长任务链接进行切分或者将两个计算量大的函数分配到不同的处理槽中。
处理时间是基于处理机器的本地时间,相对容易理解,但它会产生一些较为随意、不一致且无法重现的结果。相反,事件时间语义会生成可重现且一致性的结果,这也是很多流处理用例的刚性需求。但和基于处理时间语义的应用相比,基于事件时间的应用需要一些额外的配置。此外,相比纯粹使用处理时间的引擎,支持事件时间的流处理引擎内部要更加复杂。
Flink不仅针对常见的事件时间操作提供了直观易用的原语,还支持一些表达能力很强API,允许使用者以自定义算子的方式实现更高级的事件时间处理应用。在面对这些高级应用时,充分理解Flink内部事件处理机制通常会有所帮助,有时候更是必要的。
接下面我们会介绍Flink内部如何实现和处理时间戳及水位线以支持事件时间语义的流式应用。
在事件时间模式下,Flink流式应用处理的所有记录都必须包含时间戳。时间戳将记录和特定时间点进行关联,这些时间点通常是记录所对应事件的发生时间。但实际上应用可以自由选择时间戳的含义,只要保证流记录的时间戳会随着数据流的前进大致递增即可。
注:这里有两种方式来分配时间戳和生成水印:
1:直接在数据流源中进行。
:2:通过timestamp assigner和watermark generator生成:在Flink中,timestamp分配器也定义了用来发射的水印。
1:当Flink以事件时间模式处理数据流时,会根据记录的时间戳触发时间相关算子的计算。例如,时间窗口算子会根据记录关联的时间戳将其分配到窗口中。Flink内部采用8字节的Long值对时间戳进行编码,并将它们以元数据(metadata)的形式附加在记录上。内置算子会将这个Long值解析为毫秒精度的Unix时间戳(自1970-01-01-00:00:00.000以来的毫秒数)。但自定义算子可以有自己的时间戳解析机制,如将精度调整为微秒。
2:除了记录的时间戳,Flink基于事件时间的应用还必须提供水位线(watermark)。水位线用于在事件时间应用中推断每个任务当前的事件时间。基于时间的算子会使用这个时间来触发计算并推动进度前进。
例如:基于时间窗口的任务会在其事件时间超过窗口结束边界时进行最终的窗口计算井发出结果。
在Flink中,水位线是利用一些包含Long值时间戳的特殊记录来实现的。如图3-8所示,它们像带有额外时间戳的常规记录一样在数据流中移动。
Watermark是一种告诉Flink一个消息延迟多少的方式。它定义了什么时候不再等待更早的数据。
可以把Watermarks理解为一个水位线,这个Watermarks在不断的变化。Watermark实际上作为数据流的一部分随数据流流动。
当Flink中的运算符接收到Watermarks时,它明白早于该时间的消息已经完全抵达计算引擎,即假设不会再有时间小于水位线的事件到达。
这个假设是触发窗口计算的基础,只有水位线越过窗口对应的结束时间,窗口才会关闭和进行计算
3:
水位线拥有两个基本属性:
1.必须单调递增。这是为了确保任务中的事件时间时钟正确前进,不会倒退。
2.和记录的时间戳存在联系。一个时间戳为T的水位线表示,接下来所有记录的时间戳一定都大于T。
第二个属性可用来处理数据流中时间戳乱序的记录,例如图3-8中的时间戳为3和5的记录。对基于时间的算子任务而言,其收集和处理的记录可能会包含乱序的时间戳。这些算子只有当自己的事件时间时钟(由接收的水位线驱动)指示不必再等那些包含相关时间戳的记录时,才会最终触发计算。当任务收到一个违反水位线属性,即时间戳小于或等于前一个水位线的记录时,该记录本应参与的计算可能已经完成。我们称此类记录为迟到记录(laterecord)。
水位线的意义之一在于它允许应用控制结果的完整性和延迟。
主要讨论算子对水位线的处理方式:
Flink内部将水位线实现为特殊的记录,它们可以通过算子任务进行接收和发送。任务内部的时间服务(timeservice)会维护一些计时器(timer),它们依靠接收到水位线来激活。这些计时器是由任务在时间服务内注册,并在将来的某个时间点执行计算。例如:窗口算子会为每个活动窗口注册一个计时器,它们会在事件时间超过窗口的结束时间时清理窗口状态。
当任务接收到一个水位线时会执行以下操作:
1.基于水位线记录的时间戳更新内部事件时间时钟。
2.任务的时间服务会找出所有触发时间小于更新后事件时间的计时器。对于每个到期的计时器,调用回调函数,利用它来执行计算或发出记录。
3.任务根据更新后的事件时间将水位线发出。
任务在收到一个新的水位线之后,将如何发送水位线和更新其内部事件时间时钟?
Flink会将数据流划分为不同的分区,并将它们交由不同的算子任务来并行执行。每个分区作为一个数据流,都会包含带有时间戳的记录以及水位线。根据算子的上下游连接情况,其任务可能需要同时接收来自多个输入分区的记录和水位线,也可能需要将它们发送到多个输出分区。
任务如何将水位线发送至多个输出任务,以及它从多个输入任务获取水位线后如何推动事件时间时钟前进?
一个任务会为它的每个输入分区都维护一个分区水位线(partitionwatermark)。当收到某个分区传来的水位线后,任务会以接收值和当前值中较大的那个去更新对应分区水位线的值。随后,任务会把事件时间时钟调整为所有分区水位线中最小的那个值。如果事件时间时钟向前推动,任务会先处理因此而触发的所有计时器,之后才会把对应的水位线发往所有连接的输出分区,以实现事件时间到全部下游任务的广播。
重点:检查点的算法,保存点的创建
Flink是一个分布式的数据处理系统,因此必须能够处理一些故障,例如:进程被强制关闭、机器故障以及网络连接中断。由于每个任务会把状态、维护在本地,Flink要保证发生故障时状态不丢不错。
介绍Fli础的检查点(checkpoint)及故障恢复机制,看一下它们如何提供精确一次的状态一致性保障。
Flink的故障恢复机制需要基于应用状态的一致性检查点。有状态的流式应用的一致性检查点是对全部任务状态进行的一个拷贝。可以通过一个朴素算怯对应用建立一致性检查点的过程进行解释。
朴素算法的步骤包括:
1:暂停接收所有输入流。
2:等待已经流入系统的数据被完全处理,即所有任务已经处理完所有的输入数据。
3:将所有任务的状态拷贝到远程持久化存储(时间长的原因),生成检查点。在所有任务完成自己的拷贝工作后,检查点生成完毕。(经过网络传输)
4:恢复所有数据流的接收。(注:Flink没有实现这种朴素策略,而是使用了一种更加复杂的检查点算法)
该应用有一个数据源任务,负责从一个递增数字(1、2、3…)流中读取数据。数字流会被分成奇数流和偶数流,求和算子的两个任务会分别对它们求和。数据源算子的任务会把输入流的当前偏移量存为状态;求和算子的任务会把当前和值存为状态。在图中,Flink会在输入偏移到达5的时候生成一个检查点,此时两个和值分别为6和9.
input offset:输入偏移量
在流式应用执行过程中,Flink会周期性地为应用状态生成检查点。一旦发生故障,Flink会利用最新的检查点将应用状态恢复到某个一致性的点并重启处理进程。整个恢复过程如下图。
恢复到创建检查点时的状态—距离最近检查点的状态
应用恢复要经过3个步骤:
1:重启整个应用。
2:利用最新的检查点重置任务状态。
3:恢复所有任务的处理。
如果所有算子都将它们全部的状态写入检查点并从中恢复,并且所有输入流的消费位置都能重置到检查点生成那一刻,那么该检查点和恢复机制就能为整个应用的状态提供精确一次的一致性保障(精确一次,每个数据只处理一次,每个状态被影响只有一次)
数据源能否重置其输入流取决于它的具体实现以及所消费外部系统是否提供相关接口。
例如,类似ApacheKafka的事件日志系统就允许从之前的某个偏移读取记录。相反,如果数据流是从套接字(socket)消费而来则无法重置,因为套接字会在数据被取走后将包们丢弃。
应用从检查点恢复以后,它的内部状态会和生成检查点的时候完全一致。随后应用就会重新消费并处理那些从之前检查点完成开始,到发生系统故障之间已经处理过的数据。虽然这意味着Flink会重复处理部分消息,但上述机制仍然可以实现精确一次的状态一致性,(故障时看似不精确,本质上精确,状态处理一次)因为所有算子的状态都会重置到过去还没有处理过那些数据的时间点。
注:Flink的检查点和恢复机制仅能重置流式应用内部的状态。根据应用所采用的数据汇算子,在恢复期间,某些结果记录可能会向下游系统(如事件日志系统、文件系统或数据库)发送多次。
对于某些存储系统,Flink提供的数据汇函数支持精确一次输出,例如在检查点完成后才会把写出的记录正式提交。另一种适用于很多存储系统的方法是幂等更新。
暂停执行,生成检查点,然后恢复应用
朴素方法中的“停止一切”的行为对于那些具有中等延迟要求的应用很不切实际。而Flink的检查点是基于Chandy-Lamport分布式快照算怯来实现的。该算法不会暂停整个应用,而是会把生成检查点的过程和处理过程分离,这样在部分任务持久化状态的过程中,其他任务还可以继续执行。
Flink的检查点算法中会用到一类名为检查点分隔符(checkpointbarrier)的特殊记录。和水位线类似,这些检查点分隔符会通过数据源算子注入到常规的记录流中。相对其他记录,它们在流中的位置无法提前或延后。
为了标识所属的检查点,每个检查点分隔符都会带有一个检查点编号,这样就把一条数据流从逻辑上分成了两个部分。所有先于分隔符的记录所引起的状态更改都会被包含在分隔符所对应的检查点之中;而所有晚于分隔符的记录所引起的状态更改者目会被纳入之后的检查点中。
应用包含了两个数据源任务,每个任务都会各自消费一条自增数字流。数据源任务的输出会被分成奇数流和偶数流两个部分,每一部分都会有一个任务负责对收到的全部数字求和,并将结果值更新至下游数据汇。
JobManager会向每个数据拥任务发送一个新的检查点编号,以此来启动检查点生成流程。
当一个数据源任务收到消息后,会暂停发出记录,利用状态后端触发生成本地状态的检查点,(此时的数据源,接收新的数据)并把该检查点分隔符连同检查点编号广播至所有传出的数据流分区。状态后端会在状态存为检查点完成后通知任务,随后任务会给JobManager发送确认消息。在将所有分隔符发出后,数据源将恢复正常工作。、
数据源任务发出的检查点分隔符会传输到与之相连的任务。和水位线类似,检查点分隔符总是以广播形式发送,从而可以确保每个任务能从它们的每个输入都收到一个分隔符。
当任务收到一个新检查点的分隔符时,会继续等待所有其他输入分区也发来这个检查点的分隔符。在等待过程中,它会继续处理那些从还未提供分隔符的分区发来的数据。对于已经提供分隔符的分区,它们新到来的记录会被缓冲起来,不能处理。这个等待所有分隔符到达的过程称为分隔符对齐。
任务在收齐全部输入分区发送的分隔符后,就会通知状态后端开始生成检查点,同时把检查点分隔符广播到下游相连的任务。
任务在发出所有的检查点分隔符后就会开始处理缓冲的记录。待所有缓冲的记录处理完后,任务就会继续处理输入流。
任务在发出所有的检查点分隔符后就会开始处理缓冲的记录。待所有缓冲的记录处理完后,任务就会继续处理输入流。
最终检查点分隔符到达数据汇任务。数据汇任务在收到分隔符后会依次执行分隔符对齐,将自身状态写入检查点,向JobManager确认已接收分隔符等一系列动作。JobManager在接收到所有应用任务返回的检查点确认消息后,就会将此次检查点标记为完成。
虽然Flink的检查点算法能够在不停止整个应用的情况下为流式应用生成一致的分布式检查点,但它仍会增加应用处理延迟。Flink实现了一些调整策略,可以减轻某些条件下对性能的影响。
影响延迟的因素:
任务在将其状态存入检查点的过程中,会处于阻塞状态,此时的输入会进人缓冲区。由于状态可能会很大,而且生成检查点需要把这些数据通过网络写入远程存储系统,该过程可能持续数秒,甚至数分钟。
解决方式:本地拷贝、增量检查点。(还有另外一种情况:不缓冲)
Flink的故障恢复算怯是基于状态的检查点来完成的。检查点会周期性地生成,而且会根据配置的策略自动丢弃。检查点的目的是保证应用在出现故障的时候可以顺利重启,因此当应用被手动停止后,检查点也会随之删除。
Flink最具价值且独具一格的功能之一是保存点。原则上,保存点的生成算法和检查点完全一样,因此可以把保存点看做包含一些额外元数据的检查点。保存点的生成不是由Flink自动完成,而是需要由用户(或外部调度器)显式触发。同时,Flink也不会自动清理保存点。
所有之前提到的保存点相关用例都遵循同一个模式。首先为正在运行的应用生成一个保存点,然后在应用启动时用它去初始化状态。
每个应用都会包含很多算子,而每个算子又可以定义一个或多个的键值或算子状态。算子会在一个或多个任务上并行执行,因此一个典型的应用会包含多个状态,它们分布在不同TaskManager进程内的算子任务上
保存点中的状态副本会按照算子标识和状态名称进行组织。该算子标识和状态名需要能将保存点的状态数据映射到应用启动后的算子状态上。当应用从保存点启动时,Flink会将保存点的数据分发到对应算子的任务上。
如果应用在从保存点启动的时候发生过改动,那么保存点中的状态只有在应用还保留着那些含有对应标识和状态名称的算子时才可以成功映射。默认情况下,Flink会给每个算子分配一个唯一标识。但该标识是根据前置算子的标识按照某种确定规则生成的。这意味着任何一个前置算子发生改变(例如添加或删除某个算子)都会导致该标识发生变化。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。