赞
踩
本文为Flink 重要概念梳理与解析,内容来源于 flink 1.15版本官方文档与个人总结,持续更新…
Flink 运行时由两种类型的进程组成:一个 JobManager 和至少一个 TaskManager。
Client 不是运行时和程序执行的一部分,而是用于准备数据流并将其发送给 JobManager。之后,客户端可以断开连接(分离模式),或保持连接来接收进程报告(附加模式)。客户端可以作为触发执行 Java/Scala 程序的一部分运行,也可以在命令行进程./bin/flink run …中运行。可以通过多种方式启动 JobManager 和 TaskManager:直接在机器上作为standalone 集群启动、在容器中启动、或者通过YARN等资源框架管理并启动。TaskManager 连接到 JobManagers,宣布自己可用,并被分配工作。
JobManager决定何时调度下一个 task(或一组 task)、对完成的 task 或执行失败做出反应、协调 checkpoint、并且协调从失败中恢复等等。高可用(HA)配置中可能有多个 JobManager,其中一个始终是 leader,其他的则是 standby。这个进程由三个不同的组件组成:
TaskManager也称为 worker,执行作业流的 task,并且缓存和交换数据流。至少有一个 TaskManager。每个 TaskManager都是一个 JVM 进程,可以在单独的线程中执行一个或多个 subtask。在 TaskManager 中资源调度的最小单位是 task slot。TaskManager 中 task slot 的数量表示并发处理 task 的数量。请注意一个 task slot 中可以执行多个算子。
每个 task slot代表 TaskManager中资源的固定部分。例如,具有 3个 slot的 TaskManager会将其托管内存的 1/3用于每个 slot。subtask 不会与其他作业的 subtask 竞争内存,而是具有一定的保留内存。注意此处没有 cpu隔离,只有内存隔离。
如果每个 TaskManager有一个 slot,那么每个 task组都在单独的 JVM中运行。如果有多个 slot,那么有更多 subtask 共享同一个 JVM。同一个 JVM中的 task共享 tcp连接(通过多路复用)和心跳信息,还可以共享数据集和数据结构,默认情况下允许 subtask共享 slot,即便它们是不同的 task的 subtask,只要是来自于同一作业即可。
Flink 可以将算子的 subtasks 优化链接成 tasks(比如把两个相邻算子的subtask连接成一个task)。每个 task 由一个线程执行。将算子链接成 task 是个有用的优化:它减少线程间切换、缓冲的开销,并且减少延迟的同时增加整体吞吐量。
应用程序的作业可以被提交到长期运行的 Flink Session集群、专用的 Flink Job集群或 Flink
Application集群,它们之间的差异主要在 集群生命周期 和 资源隔离 上。
也称为 session模式下的 Flink集群。
1)集群生命周期:客户端连接到一个预先存在的、长期运行的集群,该集群可以接受多个作业提交。即使所有作业完成后,集群(和 JobManager)仍将继续运行直到手动停止 session为止。因此,Flink Session集群的寿命不受任何 Flink 作业寿命的约束。
2)资源隔离:TaskManager slot由 ResourceManager在提交作业时分配,并在作业完成时释放。由于所有作业都共享同一集群,因此在集群资源方面存在一些竞争 — 例如提交工作阶段的网络带宽。此共享设置的局限性在于,如果 TaskManager崩溃,则在此 TaskManager上运行 task的所有作业都将失败;类似的,如果 JobManager上发生一些致命错误,它将影响集群中正在运行的所有作业
3)其他:拥有一个预先存在的集群可以节省大量时间申请资源和启动 TaskManager。有种场景很重要,作业执行时间短并且启动时间长会对端到端的用户体验产生负面的影响 — 就像对简短查询的交互式分析一样,希望作业可以使用现有资源快速执行计算。
也称为 job (or per-job) 模式下的 Flink集群。k8s不支持该模式。
1)集群生命周期:在 Flink Job集群中,可用的集群管理器(例如 YARN)用于为每个提交的作业启动一个集群,并且该集群仅可用于该作业。在这里,客户端首先从集群管理器请求资源启动 JobManager,然后将作业提交给在这个进程中运行的 Dispatcher。然后根据作业的资源请求惰性的分配 TaskManager。一旦作业完成,Flink Job集群将被删除。
2)资源隔离:JobManager 中的致命错误仅影响在 Flink Job集群中运行的一个作业。
3)其他:由于 ResourceManager必须应用并等待外部资源管理组件来启动 TaskManager进程和分配资源,因此 Flink Job集群更适合长期运行、具有高稳定性要求且对较长的启动时间不敏感的大型作业。
Flink Job 集群可以看做是 Flink Application集群”客户端运行“的替代方案。
1)集群生命周期:Flink Application集群是专用的 Flink集群,仅从 Flink应用程序执行作业,并且 main()方法在集群上而不是客户端上运行。提交作业是一个单步骤过程:无需先启动 Flink集群,然后将作业提交到现有的 session集群;相反,将应用程序逻辑和依赖打包成一个可执行的作业 JAR中,并且集群入口(ApplicationClusterEntryPoint)负责调用 main()方法来提取 JobGraph。例如,这允许你像在 k8s上部署任何其他应用程序一样部署 Flink应用程序。因此,Flink Application集群的寿命与 Flink应用程序的寿命有关。
2)资源隔离:在 Flink Application集群中,ResourceManager和 Dispatcher作用于单个的 Flink应用程序,相比于 Flink Session集群,它提供了更好的隔离。
两种流行的机制来实现『精确一次』处理语义。
1)分布式快照 / 状态检查点(Chandy-Lamport 分布式快照算法):如 flink
2)至少一次事件传递和对重复数据去重: 如 kafka streams
『精确一次』并不能保证事件的处理,即任意用户定义逻辑的执行,只会发生一次。更适合用『有效一次』(effectively once)这个术语来表示这种保证,因为处理不一定保证只发生一次,但是对引擎管理的状态的影响只反映一次,即引擎管理的状态更新只提交一次到持久的后端存储。
状态流处理 (Stateful Stream Processing):有些操作会需要使用跨多个事件的信息,这些操作称为有状态操作。Flink
需要了解状态,来使用检查点和保存点容错机制。可查询状态的允许您在运行时从 Flink 外部访问状态。
keyed state被维护在内嵌的键值储存中,键值状态只能在 keyed stream中访问。keyed state进一步组织成key groups,后者是 Flink 可以重新分配keyed state的原子单元; key group的数量等于定义的最大并行度。
Flink 使用流重播 stream replay和检查点 checkpoint的组合机制来实现容错:检查点标记每个输入流中的特定点以及每个算子的相应状态。流式数据流可以从检查点恢复,同时通过恢复算子的状态,并从检查点重放记录来保持一致性(exactly-once处理语义)。如果程序失败(由于机器、网络或软件失败) ,Flink 将停止分布式数据流,然后重新启动算子,并将其重置为最新的成功检查点。
容错机制基于分布式快照的标准 Chandy-Lamport 算法改进实现,会连续地绘制分布式流数据流的快照。对于具有较小状态的流应用程序,这些快照非常轻量级,可以频繁地绘制,而不会对性能产生很大影响。流式应用的状态存储在一个可配置的位置,通常是在一个分布式的文件系统dfs中。
检查点通过分布式快照 distributed snapshots实现,默认被禁用。与检查点有关的所有事情都可以异步完成。检查点可以是对齐的,也可以是不对齐的。
快照屏障被注入到数据流中,并作为数据流的一部分与记录一起流动,且永远不会超过记录,严格按照顺序流动。其主要作用是将数据流中的记录分隔为进入当前快照的记录集和进入下一个快照的记录集,每个屏障都携带快照的 ID。屏障不会中断流的流动,是非常轻量级的。来自不同快照的多个屏障可以同时出现在流中,也就是各种快照可能并发产生。
屏障在生成快照中的作用:一旦sink 算子(流式 DAG 的末尾)从其所有输入流接收到屏障 n(对齐输入流),它就向检查点协调器 checkpoint coordinator确认快照 n。在所有sink 算子都确认该快照之后,就认为该快照已经完成。
在并行的数据流中,屏障n会被注入到数据流的所有分支中,同时,接受并行输入流的算子,需要在保存快照时,需要等待输入流的对齐。
例如,某算子需要a、b、c输入流的数据,a、b、c输入流中都插入了屏障1,但算子可能无法同时从a、b、c输入流接收到屏障1。如果首先从输入流a中接收到屏障1,那么算子需要等待其余输入流的对齐,直到算子也从输入流b、c中接收到屏障1,在这之前,算子不能处理来自输入流a中任何新的记录(否则将混合快照1和快照2的记录)。当算子从其所有的输入流中都接收到了屏障1,输入流已对齐,可以生成快照1。快照进行后,算子继续处理来自所有输入流的记录,对于输入流a来说,将先处理来自输入缓冲区的记录。
算子保存快照,将状态写入状态后端state backend的过程是异步的。
算子在从其输入流接收到所有快照屏障的时间点,并在将屏障发送到其输出流之前对其状态进行快照。因为快照的状态可能很大,所以它存储在可配置的状态后端中。默认情况下,这是 JobManager 的内存,但是对于生产使用,应该配置分布式的可靠存储(比如 HDFS)。状态被存储后,算子确认检查点,发送快照屏障到它的输出流中,然后继续处理。
恢复非常简单: 当出现故障时,(1)Flink 选择最新完成的检查点 k。然后,系统重新部署整个分布式数据流,并(2)向每个算子提供检查点 k 的一部分快照的状态。(3)sources也将从位置 Sk开始读取数据流。例如,在kafka中表示让消费者开始从偏移量 Sk 获取数据。
保存检查点也可以在未对齐时进行,其基本思想是,只要未处理到的数据成为算子状态的一部分,检查点就可以接管所有未处理到的数据。
相当于算子将屏障前所有未被处理到的记录作为检查点的一部分保存起来,这部分数据也是比对齐方式多保存的一部分数据。这种情况下,算子会对输入缓冲区中的接受到的第一个屏障作出反应,屏障可以立即被转发到输出缓冲区的末端。
对齐步骤可能会增加流程序的延迟,通常,额外延迟大约是几毫秒,可以配置是否可以在检查点期间跳过流对齐。
存储键/值索引的确切数据结构取决于所选择的状态后端。可以在内存哈希映射中存储数据,也可以使用 RocksDB 存储键/值。除了定义保存状态的数据结构之外,状态后端还实现了获取键/值状态的时间点快照的逻辑,和快照的存储。可以在不更改应用逻辑的情况下配置状态后端。
保存点类似于检查点,是用户手动触发的检查点。它获取程序的快照并将其写入状态后端,依赖于常规的检查点机制,所有使用检查点的程序都可以从保存点恢复执行。并且在新的检查点完成时不会自动过期。保存点一定是对齐的。保存点允许在不丢失任何状态的情况下更新应用和 Flink 集群。
上述概念适用于批处理程序的方式与适用于流程序的方式相同,有少数例外:
1)批处理程序的容错不使用检查点。恢复通过完全重放流来进行。这是可能的,因为输入是有界的。这使得恢复的成本更高,但是使得常规处理更便宜,因为它避免了检查点。
2)DataSet API 中的有状态操作 stateful operations使用简化的内存中/核外 in-memory/out-of-core数据结构,而不是键/值索引。
3)DataSet API 引入了特殊的同步迭代,这些迭代只能在有界流上进行。
附图:对齐时保存检查点:
附图:未对齐时保存检查点:
时间流处理(Timely Stream Processing)是有状态流处理的一种延伸,时间在有状态流处理中起着一定的作用。在进行时间序列分析、基于特定时间段(通常称为窗口)进行聚合、或者在事件发生时间非常重要的情况下进行事件处理时,就会出现这种情况。
处理时间指执行相应操作的机器的系统时间,特点:最佳性能,最低延迟,有不确定性,容易受其他影响。
事件时间指每个时间在其生成设备上发生的时间。这个时间通常在记录输入 Flink 之前嵌入到记录中,并且可以从每个记录中提取该事件时间戳,事件时间程序必须指定如何生成事件时间的水印。
水印是 Flink中衡量事件时间进度的机制,对于无序流是非常重要的。水印是一个时间戳,也是一种声明,声明在流中直到这个时间戳的所有事件应该已经到达,后面应该不会再有比水印更早的记录。算子可以根据水印触发窗口的计算、清理资源等操作。水印是在源函数处生成的,或者直接在源函数之后生成的,源函数的每个并行子任务通常独立地生成其水印。
某些元素可能会违反水印条件,这意味着即使在watermark(t)出现之后,还会出现更多带有时间戳 t’< = t 的元素。可以允许延迟元素的存在,并处理它们。
窗口是一种在流上的处理聚合事件(例如,计数、总和)的工作方式。窗口确定聚合事件的作用域,如“在过去5分钟内计数”或“最后100个元素的总和”。
窗口可以是时间驱动的(例如: 每30秒)或数据驱动的(例如: 每100个元素)。
窗口有不同类型,如滚动窗口(无重叠) tumbling windows、滑动窗口(有重叠) sliding windows和会话窗口(以无活动为间隔) session windows。
Flink API 有四层结构:
高抽象
^ ### SQL
| ###### Table API
| ######### DataStream\DataSet API
| ############ Stateful Stream Processing
低抽象
Flink API 最底层的抽象为有状态实时流处理,其抽象实现是 Process Function。Process Function被集成到了 DataStream API 中来使用。
DataStream API(应用于有界/无界数据流场景)和 DataSet API(应用于有界数据集场景)两部分是 Core API,许多应用程序不需要使用到最底层抽象的 API,而是可以使用 Core API进行编程。Process Function 这类底层抽象和 DataStream API 的相互集成使用。
Table API 是以表(Table)为中心的声明式编程(DSL)API,例如在流式数据场景下,它可以表示一张正在动态改变的表。使用起来很简洁并且可以由各种类型的用户自定义函数扩展功能,但还是比 Core API 的表达能力差。Table API 程序在执行之前还会使用优化器中的优化规则对用户编写的表达式进行优化。Flink 允许用户在编写应用程序时将 Table API 与 DataStream/DataSet API 混合使用。
SQL 这层抽象在语义和程序表达式上都类似于 Table API,SQL 查询语句可以在 Table API 中定义的表上执行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。