本文转载自微信公众号《数据仓库宝库》,作者张立兵。转载本文请联系数据仓库宝贝图书馆公众号。1架构介绍Flink系统架构主要分为三层:APIs&Libraries、Core、Deploy,如图1所示。APIs层主要实现了用于流处理的DataStreamAPI和用于批处理的DataSetAPI。Libraries层也称为Flink应用组件层。根据API层的划分,在API层上构建满足特定应用领域的计算框架,对应流处理和批处理两类,其中流处理支持CEP(复杂事件处理),SQL-like操作(基于表的关系操作);支持FlinkML(机器学习库)和Gelly(图处理)进行批处理。Runtime层提供Flink计算的所有核心实现,如支持分布式Stream作业执行、JobGraph到ExecutionGraph的映射调度等,并为API层提供基础服务。Deploy层支持多种部署方式,包括本地、集群(Standalone、YARN、Kubernetes)和云部署(GCE/EC2)。图1 Flink的整体架构1.编程接口Flink提供了多种抽象的编程接口,适用于不同层次的用户。数据分析师和面向业务的数据开发人员可以使用FlinkSQL来定义流作业。如图2所示,Flink编程接口分为四层。图2 Flink编程接口抽象FlinkSQL一个大数据技术要想被用户接受和使用,除了先进的架构理念外,还有一个很重要的一点就是要有非常好的易用性。我们知道,虽然Pig中的操作更加灵活高效,但数据开发者在满足数据处理需求的前提下,更愿意选择Hive作为大数据处理的开发工具。最重要的原因是Hive可以基于SQL标准进行扩展,提出HQL语言,让很多只会SQL的用户也能快速掌握大数据处理技术。因此,Hive技术迅速流行起来。Flink也是如此。要想赢得更多用户,就必须不断提升易用性。FlinkSQL基于关系概念构建流式处理和离线处理应用,让用户更容易通过SQL构建Flink作业。TableAPIFlinkSQL解析生成逻辑执行计划和物理执行计划,然后转化为Table之间的操作,最后转化为JobGraph运行在集群上。TableAPI类似于Spark中的DataSet/DataFrame接口,都提供领域语言的编程接口。与FlinkSQL相比,TableAPI更加灵活。可以与Java&ScalaSDK中的DataStream和DataSetAPI相互转换,也可以结合FlinkSQL进行数据处理。DataStream&DataSetAPI在早期的Flink版本中,DataSetAPI和DataStreamAPI分别用于流处理和批处理场景。DataSet用于处理离线数据集,DataStream用于处理流式数据集。DataFlow模型希望使用同一个流处理框架来统一处理有界和无界数据,那么Flink为什么要抽象出两套编程接口来处理有界和无界数据集呢?这也是Flink社区近年来一直在讨论的话题。.目前虽然可以在Table和SQLAPI层面实现批流融合,但这只是在逻辑层面,最终会转化为DataSetAPI和DataStreamAPI对应的作业。后续Flink社区会逐步通过DataStream处理有界和无界数据集。社区在1.11版本重构了DataStreamAPI中的SourceFunction接口,使DataStream可以访问和处理有界数据集。在以后的版本中,Flink会逐步实现批流的真正融合。StatefulProcessingFunction接口StatefulProcessingFunction接口提供了强大灵活的编程能力,其中可以直接操作状态数据、TimeService等服务,同时可以注册事件时间和处理时间回调定时器,使程序能够实现更复杂的计算。DataStreamAPI需要使用状态处理函数接口。StatefulProcessingFunction接口虽然灵活性高,但是使用接口的复杂度比较高,DataStreamAPI基于StatefulProcessFunction接口封装了非常丰富的算子集。这些运算符可以直接使用。因此,除非用户需要自定义更复杂的算子(比如直接操作状态数据等),否则没有必要使用StatefulProcessingFunction接口来开发Flink作业。2.运行时执行引擎用户使用组件栈和接口编写的Flink作业,最终会在客户端转化为JobGraph对象,然后提交到集群运行。除了任务的提交和执行,运行时还包括资源管理器Resource-Manager和负责接收和执行Task的TaskManager。这些服务各司其职并相互合作。Runtime提供不同类型(有界和无界)作业的执行和调度功能,最终将任务拆解为任务执行和调度。同时,runtime兼容不同类型的集群资源管理器,可以提供不同的部署方式,统一管理slot计算资源。3.物理部署层物理部署层的主要作用是兼容不同的资源管理器,如支持集群部署方式的HadoopYARN、Kubernetes、Standalone等。这些资源管理器可以为运行在Flink运行时上的作业提供Slot计算资源。第4章将重点介绍Flink的物理部署层的实现,帮助您了解如何在不同的资源管理器上运行运行时,并有效地管理资源管理器提供的计算资源。2、Flink集群架构如图3所示,Flink集群主要包括三个部分:JobManager、TaskManager和client,它们都是独立的JVM进程。Flink集群启动后,至少会启动一个JobManager和多个Task-Manager。客户端将任务提交给JobManager,JobManager将任务拆分成Tasks分派给各个TaskManager执行。最后,TaskManager将Task的执行状态报告给JobManager。图3 Flink集群架构图客户端是Flink的客户端实现,专门用于提交任务。它可以在任何设备上运行,兼容Windows、macOS、Linux等操作系统。网络畅通无阻。用户可以通过./bin/f?linkrun命令或ScalaShell交互式命令行提交作业。客户端会在内部运行提交的作业,然后根据作业的代码逻辑构建JobGraph结构,最后将JobGraph提交给runtime运行。JobGraph是客户端和集群运行时之间约定的统一抽象数据结构。也就是说无论是什么类型的job,提交的应用都会通过客户端构建成一个JobGraph结构,最后提交到集群中运行。JobManager是整个集群的管理节点,负责接收并执行客户端提交的JobGraph。JobManager还负责整个任务的Checkpoint协调。它负责在内部协调和调度提交的任务,将JobGraph转化为ExecutionGraph结构,然后通过scheduler调度执行ExecutionGraph的节点。ExecutionGraph中的ExecutionVertex节点会以Task的形式在TaskManager中执行。JobManager除了作业的调度和管理,还会统一管理整个集群的计算资源。TaskManager的所有计算资源都会注册到JobManager节点中,然后分配给不同的任务。当然JobManager还有很多功能,比如Checkpoint的触发和协调。TaskManager作为整个集群的工作节点,主要用于为集群提供计算资源。每个TaskManager都包含一定数量的内存、CPU等计算资源。这些计算资源将被打包到Slot资源卡槽中,然后通过主节点中的ResourceManager组件进行统一协调和管理,将任务中的并行Task分配给Slot计算资源。根据不同的底层集群资源管理器,TaskManager的启动方式和资源管理形式也会有所不同。比如在基于Standalone模式的集群中,所有的TaskManager都按照固定的编号启动;而在YARN、Kubernetes等资源管理器上创建的Flink集群支持TaskManager节点的动态按需启动。三个核心概念1.状态计算在Flink架构体系中,状态计算是非常重要的特性之一。如图4所示,有状态计算是指在程序计算过程中,程序将计算产生的中间结果存储在内部,提供给后续算子进行计算。状态数据可以存储在本地内存中,也可以存储在第三方存储介质中,比如Flink已经实现的RocksDB。图4 有状态处理和无状态处理与有状态计算不同,无状态计算不存储计算过程中产生的结果,也不使用这些结果进行下一步的计算。该程序只会在当前计算过程中执行。计算完成后会输出结果,然后连接下一条数据继续处理。无状态计算的实现复杂度比较低,实现起来也比较容易,但是无法应对更复杂的业务场景,比如处理实时的CEP问题,按分钟、小时、天进行聚合计算,以及计算最大值和平均值等聚合。指标等,如果不依赖Flink提供的内部状态存储,一般需要借助外部数据存储介质,比如Redis这样的key-value存储系统来完成复杂指标的计算。与Storm等流处理框架不同,Flink支持有状态计算,可以处理更复杂的数据计算场景。2.时间概念和水位机制在DataFlow模型中,时间分为事件时间和处理时间两种。如图5所示,Flink中的时间概念与DataFlow模型基本一致,Flink在上述两个时间概念的基础上增加了摄取时间的概念,即数据接入Flink系统时,由源节点创建的时间决定。图5 Flink时间概念事件时间是指每个事件在其生产设备上发生的时间。往往事件时间在进入Flink之前就嵌入到数据记录中,后续计算从每条记录中提取这个时间。基于事件时间,我们可以通过水印处理乱序事件。事件时间可以准确反映事件发生的先后顺序,这对于流处理系统来说非常重要。当涉及到大量的网络传输时,数据传输的顺序在传输过程中难免会发生变化,最终导致流式系统的统计结果出现偏差,难以通过实时计算得到正确的统计结果.处理时间是指执行相应操作员操作的机器系统时间。当应用程序基于处理时间运行时,所有基于时间的算子操作(例如时间窗口)将使用运行相应算子的机器的系统时钟。例如,如果应用程序在上午9:15运行,则第一个每小时处理时间窗口包括在上午9:15到10:00之间处理的事件,下一个窗口包括在上午10:00到11:00之间处理的事件其间处理的事件。处理时间是最简单的时间概念,不需要流和机器之间的协调,它提供最好的性能和最低的延迟。但在分布式和异步环境中,处理时间无法提供确定性,因为它容易受到记录到达系统的速度(例如,来自消息队列)以及它们在系统内操作员之间流动的速度的影响。访问时间是指数据接入Flink系统的时间,由SourceOperator根据当前时钟自动生成。后续所有与时间相关的Operator都可以根据访问时间完成窗口统计等操作。访问时间不经常使用,当访问的事件没有事件时间时,可以使用访问时间处理数据。与处理时间相比,访问时间的实现成本更高,但它的数据只生成一次,不同的窗口操作可以基于统一的时间戳,可以避免处理时间过度依赖处理算子的时钟来实现一定程度上的问题。与事件时间不同,访问时间不能完全描述事件的顺序。在Flink内部,访问时间只是像事件时间一样对待和处理,自动分配时间戳和生成水印。因此,不能完全基于访问时间来处理乱序和迟到事件。本文节选自《Flink设计与实现:核心原理与源码解析》,经出版社授权发布。
