Flink架构,源码及debug

本文详细介绍了Apache Flink的架构,从JobManager、TaskManager到ResourceManager的职责,以及高可用性和部署方式。重点讨论了数据倾斜问题,通过源码分析和调试,提出解决方案。通过理解Flink的架构,可以更有效地定位和解决问题,文中还提供了IntelliJ IDEA的调试配置技巧。
摘要由CSDN通过智能技术生成

工作中用Flink做批量和流式处理有段时间了,感觉只看Flink文档是对Flink ProgramRuntime的细节描述不是很多, 程序员还是看代码最简单和有效。所以想写点东西,记录一下,如果能对别人有所帮助,善莫大焉。

        说一下我的工作,在一个项目里我们在Flink-SQL基础上构建了一个SQL Engine, 使懂SQL非技术人员能够使用SQL代替程序员直接实现Application, 然后在此基础上在加上一些拖拽的界面,使不懂SQL非技术人员 利用拖拽实现批量或流式数据处理的Application 。 公司的数据源多样且庞大,发布渠道也很丰富, 我们在SQL Engine 里实现了各种各样的Table Source (数据源) , Table Sink (数据发布)和 UDF (计算器), 公司里有很多十分懂业务专业分析员,如果他们真的可以简简单单,托托拽拽的操作大数据,建立计算模型,然后快速上线和发布,这样的产品应该前景广阔。

        可是后台并非说起来这么简单,SQL使用不善,难以达到业务想要的效果,数据量一上来各种问题会出现,后端需要大量的优化工作, 比如 数据倾斜, 是最常发生的事情。SQL基本上是一个Join Language。用户经常会将一个大数据源和一个小数据源做Inner Join, 如果大数据源的数据项很大部分都使用极少数的几个join key, 就很容易出现数据倾斜。现实倾斜的或不均衡的,比如国际资本>80%以用美元计价,世界人口50%属于某两个国家, 财富主要有20%的人拥有, 等等 。 Flink 如果把SQL join 执行成Hash Join, 最后的结果是无论你实现分配了多少个TaskSlots, 如果80%的数据都跑到某一个TaskSlot里,缓慢运行直至将个这Slot的资源耗尽,整个job失败。这种情况最好是将小数据集广播给所有的下游通道, 大数据集按原始的分片并行,这样的join因分配均衡而快速。然而标准SQL里没有办法指定joinhint , Flink sql也不支持这个,只能通过debug flink 来看看哪里能做一些改变解决这个问题。我们在最后一章,从Flink client , flink optimizer, flink run-time (job manager, task manager) 一步一步的 在源码里设置断点, debug, 将数据流过一遍,看看有哪些方案可以将这个小数据集合广播起来。

       为了使本文读起来流畅一些, 我先通过几个章节大概介绍一下Flink 。本文关心架构, 所以不会涉及很多关于API的东西(比如Flink streaming 的windowing, watermark, Dataset, DataStream, 及SQL的API等, 网上应该有很多关于这些的文章)。只是想大概梳理一个Flink的架构,使架构对应到源码结构里, 了解一下Flink的 Graph metadata, 高可靠性的设计,不同cluster环境里 depoloyment的实现等, 最后利用IntelliJ IDEA 通過一個小例子带大家debug一下Flink 。如果对flink的架构有较好的理解(比如主要类及metadata),就比较容易在准确的地方设置断点,debug Flink代码将更有效率,从而解决问题就会更有效率, 这就是本文的目的。大概了解一下框架,但并不会面面俱到。当如果你需要深入了解一下Flink某方面的细节, 本文能够告诉你入口在哪里,或者通过对架构了解过程中得到的common sense , 再加上一点想象力, 你或许直接能够得到解决问题的方案, 然后再通过阅读源码及调试来加以验证。 

1.  Flink的架构简介

1.1  Flink 分布式运行环境(官方图)

(图-1 Flink Runtime 来自:https://ci.apache.org/projects/flink/flink-docs-release-1.6/concepts/runtime.html

关于架构,先上一个官方的图,尽管对于Flink架构,上图不是很准确(比如client与JobManager的通讯已经改为REST 方式, 而非AKKA的actor system),我们还是可以知道一些要点:

  • FlinkCluster: Flink的分布式运行环境是由一个(起作用的)JobManager 和多个TaskManager组成。每一个JobManager(JM)或TaskManager(TM)都运行在一个独立的JVM里,他们之间通过AKKA的Actor system (建立的RPC service)通讯。所有的TaskManager 都有JobManager管理,Flink distributed runtime实际上是一个没有硬件资源管理 的软件集群 ( FlinkCluster ), JM是这个FlinkCluster 的master, TM是worker。  所以将Flink运行在真正的cluster 环境里(能够动态分配硬件资源的cluster,比如Yarn, Mesos, kubernetes), 只需要将JM 和 TM运行在这些集群资源管理器分配的容器里,配置网络环境和集群服务使AKKA能工作起来, Flink cluster 看起来就可以工作了。具体的关于怎么将Flink 部署到不同的环境, 之后有介绍, 虽然没有上面说的这么简单,还有一些额外的工作, 不过大概就是这样:因为Flink runtime 自身已经通过AKKA的 sharding cluster建立了FlinkCluster, 部署到外围的集群管理只是为了获取硬件资源服务。 Flink不是搭建在零基础上的框架,任何功能都要自己重新孵化,实际上它使用了大量的优秀的开源框架, 比如用AKKA实现软件集群及远程方法调用服务(RPC), 用ZooKeeper提高JM的高可用性, 用HDFS,S3, RocksDB 永久存储数据, 用 Yarn/Mesos/Kubernetes做容器资管理, 用Netty 做高速数据流传输等等。
  • JobManager: JobManager 作为FlinkCluster的manager,它是由一些Services 组成的,有的service 接受从flink client端 提交的Dataflow Graph(JobGraph),并将JobGraph schedule 到TaskManager里运行,有的Service 协调做每个operator 的checkpoint以备job graph运行失败后及时恢复到失败前的现场从而继续运行 , 有的Service负责资源管理, 有的service 负责高可用性,后面在详细介绍。值得一提的是,集群里有且只有一个工作的JM, 它会对每一个job实例化一个Job
  • TaskManager : TaskManager是slot  的提供者和sub task的执行者。通常Flink Cluster里会有多个TM, 每个TM都拥有能够同时运行多个SubTask的限额,Flink称之为TaskSlot。当TM启动后, TM 将slot限额注册到Cluster里的JM的ResourceManager(RM), RM知道从而Cluster中的slot 总量,并要求TM将一定数量的slot 提供给JM,从而JM 可以将Dataflow Graph的task(sub task)分配给TM 去执行。TM是运行并行子任务(sub Task) 的载体 (一个Job workflow 需要分解成很多task, 每一个task 分解成一个过多个并行子任务:sub Task) , TM需要把这些sub task在自己的进程空间里运行起来, 而且负责传递他们之间的输入输出数据, 这些数据 包括是本地task的和运行在另一个TM里的远程Task 。关于如何具体excute Tasks, 和交换数据 后面介绍。
  • Client:Client端(Flink Program)通过invoke 用户jar文件 (flink run 中提供的 jar file)的里main函数 (注册data source, apply operators, 注册data sink, apply data sink),从而在ExecutionEnvironment或StreamingExecutionEnvironment里建立sink operator 为根的一个或多个FlinkPlan(以sink为根, source 为叶子, 其他operator为中间节点的树状结构), 之后client用Flink-Optimizer将Plan优化成OptimimizedPlan(根据Cost estimator计算出来的cost 优化operator在树中的原始顺序, 同时加入了Operator与Operator连接的边 , 并根据规则设置每个边的shipingStrategy, 实际上OptimizedPlan已经从一个树结构转换成一个图结构), 之后使用GraphGenerator(或StreamingGraphGenerator)将OptimizedPlan转化成JobGraph提交给JobManager, 这个提交是通过JM的DispatcherRestEndPoint提交的。
  • Communication: JobManager 与Taskanager都是AKKA cluster里的注册的actor, 他们之间很容易通过AKKA(实现的RPCService)通讯。 client与JobManager在以前(Version 1.4及以前)也是通过AKKA(实现的RPCService)通讯的,但Version1.5及以后版本的JobManager里引入DispatcherRestEndPoint (目的是使Client请求可以在穿过Firewall ?),从此client端与JobManager提供的REST EndPoint通讯。Task与Task之间的数据(data stream records)(比如一个reduce task的input来自与graph上前一个map, output 给graph上的另一个map), 如果这两个Task运行在不同的TM上,数据是通过由TM上的channel manager 管理的tcp channels传递的。

 

1.2  JobManager

 (图-2,JobManager的内部结构) 

 如上一章所述, JobManager 是一个单独的进程(JVM),  它是一个Flink Cluster的 master 、中心和大脑, 他由一堆services 组成(主要是Dispather, JobMaster 和ResourceManager),连接cluster里其他分布式组件 (TaskManager, client及其他外部组件),指挥、获得协助、或提供服务。

  • ClusuterEntryPoint是JobManager的入口,它有一个main method ,用来启动HearBeatService, HA Sercie, BlobServer,  Dispather RESTEndPoint, Dispather, ResourceManager 。不同的FlinkCluster有不同的ClusuterEntryPoint 的子类,用于启动这些Service在不同Cluster里的不同实现类或子类。Flink目前(version1.6.1)实现的FlinkCluster 包括:
    • MiniCluster : JM和TM都运行在同一个JVM里,主要用于在 IDE (IntelliJ或Eclipse)调试 Flink Program (也叫做 application )。
    • Standalone cluster : 不连接External Service (上图中灰色组件,如HA,Distributed storage, hardware Resoruce manager), JM和TM运行在不同的JVM里。 Flink release 中start-cluster.sh启动的就是StandaloneCluster.
    • YarnCluster : Yarn管理的FlinkCluster, JM的ResourceManager连接Yarn的ResourceManager创建容器运行TaskManager。BlobServer, HAService 连接外部服务,使JM更可靠。
    • MesosCluster : Mesos管理的FlinkCluster, JM的ResourceManager连接Mesos的ResourceManager创建容器运行TaskManager。BlobServer, HAService 连接外部服务,使JM更可靠。
  • HighAvailabilityService:重复之前的话:JM是一个Flink Cluster的 master 、中心和大脑, 如果JM崩溃了,整个cluster就无法运行了。HAService能够使多个JobManager同时运行,并选举一个JM作为Leader, 当Leader失败后在重新选举,使另个健康的JM取而代之成为leader, 从HA存储中读取MetaData(Graph,snapshot)从而 继续管理Cluster的运行。HighAvailabilityService 只保护JM里的DispatcherRestEndpoint, Dispatcher, ResourceManager 和JobMaster 4个核心服务, 从理论上来讲, 这些service的各自的leader有可能来自不同的JM, 这就要看外部做Coordination的服务的Leader Election策略会不会把他们都从一个JM 选了。目前,Flink支持的和在使用的HighAvailbilityService有ZooKeeperHaService和StandaloneHaService。
    • ZooKeeperHaService:连接外部的ZooKeeper cluster做多个JM的Leader Election,从指定的存储(通常是HDFS)存取JM metadata, 从而当JM takeover 或重新启动时能够获取失败之前的snapshot or savepoint, 从而继续服务。
    • StandaloneHaService : 不支持多个JM Election。但支持从指定的存储存取JM metadata, 做失败后重启恢复。
  • BlobServer 使用来存储Client端提交的Flink program jar,  jobGraph file, JM 的所有services , 和所有的TM都连接同一个BlobServer (可以是LocalDisk, HDFS, S3 , 或其他的 Blob数据库)读取这些数据。
  • HeatBeatService , 用来运行JM 与TaskManager之间的心跳服务。 比如 ResourceManager 与JobMaster和所有TaskManager之间的心跳, JobMaster与所有TaskManager之间的心跳。如果心跳消失, 相应的HA 容错措施就要启动。 比如一个TM与JM的心跳没了,那么相应的容错措施就会执行了。比如JobMaster的心跳消失,HA就会重新选举新的JobMaster Leader;TM的心跳消失,ResourceManager就要将task分配到其他空闲的TM的slot里,如果没有空闲的slot ,RM 就会向外部的ResoureManager申请新硬件和启动新的 TM以提供空闲的 slot。Flink的心跳消息是通过AKKA 传递的。
  • DispatcherRESTEndPoint是JM的4大核心服务之一(其他三个分别为Dispatcher, JobMaster和ResourceManager),受HAService的保护, 是Flink客户端与JM交互的REST接口, 也是Flink custer 的WebMonitor。非核心服务实际上都是一些UtilityService, 他们非JM独有,需要用时可随时实例化:比如Client端也会使用HAService来获取DispatcherRESTEndPoint的leader的地址和端口, TM也会使用BlobServer 。DispatcherRESTEndPoint是用Netty搭建的RESTService, 它创建了大概有290个handler 对应不容的资源地址及方法。这些handler大都需要通过RPC方式调用Dispatcher 的远程方法来满足客户的请求。
  • Diaptcher是DispatcherRESTEndPoint的后端服务层,它实现了RestDispatcher接口, 从客户端(包括FlinkClient和Flink Web Dashboard)提交给又有来自于EndPoint的请求,都由这个接口里的方法服务, 这其中最总要的方法就是submitJob。当Dispather受到submitJob的调用时,他会先在本JVM里创建一个JobMaster服务,并将 JobGraph和Flink applicaiton 的jar file , 转交给这个JobMaster去安排job具体的运行。
  • JobMaster的是用于一个Job的Master, 当集群里由多个Job同时运行则会有多个JobMaster同时运行,每一个JobMaster只会负责一个job。当接收到jobGraph时, JobMaster首先会将jobGraph转换成ExecutionGraph:一个可以指导task并行运行的数据流程图,并向ResouceManager(RM)申请运行这个ExecutionGaph需要的资源(TaskSlot):比如一个并行度为8的job,必须有8个TaskSlot才能运行起来, 然后按照ExecutionGraph将task schedle到Taskslot中去, 并定时的对task做checkpoint, 以备重启时恢复到崩溃前的现场。
  • ResourceManager负责管理FlinkCluster里所有TaskManager的TaskSlot资源(相当于TM里的一个运行线程)。当一个TM启动时,它会将自己的TaskSlot注册到RM。当JobMaster向RM申请slot时,RM会要求TM将它空闲的slot(已注册到RM,所以TM知道所有slot的状态)提供给JobMaster使用,之后JobMaster才会将相应的Task 安排到slot里运行。如果集群里的TaskSlot不够, RM会向外部的ResourceManager(比如Yarn/Mesos/Hubernetes)申请新的容器(container) 去启动新的TM从而满足JobMaster的slot资源的需求。

1.2.1  展开JobManager后的Flink架构

从以上所述, JobManager是一组Service的总称, 其中真正管理Job调度的组件叫JobMaster ,负责资源管理的组件叫ResoruceManager, 负责接收client端请求的组件叫Dispatcher(包括Dispatcher和DispatchRestEndpoint)。其实Flink源码里有叫JobManager的包和类,功能上也是负责Job调度管理以及snapshot管理,但它应该在Flink某个版本以后就legacy了(估计是从version1.3开始)。这三个服务统称为还叫 JobManager,上真正管理作业的是JobMaster。这一点在读code时让人迷惑,比如JobManagerRunner启动的却是叫JobMaster的类。但是他不叫JobMasterRunner,这也体现了JobMaster实际是取代了JobManager类,保留legacy类是为了向后兼容。以下是Client, 展开的JobManger(受HA 保护的Dispather, JobMaster, ResourceManager)和TaskManager处理submitJob的流程图,这个比较图-1更能体现当前的Flink runtime架构 (Flink 1.6):

 

(图-3)展开JobManager后的Flink 架构, 来自于《 Stream Processing with Apache Flink》

 

以上的架构严格来讲在Flink里被称作 SessionMode ( Cluster的EntryPoint类都是SessionClusterEntryPoint的子类),  如果没有外部命令 terminate cluster, 在这种模式下的FlinkCluster 是Long running 的, 多个job可以同时运行在同一个flinkcluster里。 SessionMode 在Flink的各种部署都是支持的, 包括Standalone, Kubernetes, Yarn, Mesos, 上图其实是StandaloneSessionCluster的流程。 还有一种模式叫做JobMode, 区别就是Job(或application) 的main class 和 jar 和在JobManager 启动时通过的启动参数装载的, 不需要submitJob的过程, job运行完毕, cluster自动终结, 所有资源释放。 在这种模式下, Dispather并不负责处理job的提交, 但其他 Client发给DispathcherRESTEndPoint的请求(比如Query, CancelJob), 还是由Dispatcher处理。

Flink的每一种部署模式(deployment mode)都是既支持Session Mode又支持JobMode的 (或partialy support), 区别如上所述, 但在架构上是一致的。 当有由外部的ResourceManager协助硬件资源分配时,流程略有所有不同, 以 FlinkCluster in Yarn 为例, SessionMode下, 区别只限于多了RM通过Yarn自动启动TM 的过程(4,5)。

(图-4)FlinkCluster in Yarn Session mode,  来自于《 Stream Processing with Apache Flink》

关于deployment的细节,请参照后面的将Deployment的章节。

1.2.2  JobMaster

如图一所示,JobMaster的主要工作是:

1.  JobGraph的scheduler : 将Client提交的JobGraph按照逻辑的向后关系(source -> transform -> sink), 以及并行关系(每个operator的子任务只负责全部数据的中一部分), 将子任务分配到TaskManager的Slot中, 并定期的获取每一个子任务的运行状态 (status)。

2. 触发和管理Job的checkpoint snapshot:对于streaming job,定期的将运行中的每个operator 的状态(State)数据存入规定的存储设备, 这些state数据可以用于在Job恢复运行时,恢复相关子任务的失败前的现场。

 

 (图-5)JobMaster内部结构

  •  ExcutionGraph (EG) 是JobMaster 最核心的组件,它承担了JobMaster 上述的的两大责任: job scheduling 和 checkpoint snapshot  。EG的细节下节展开。
  • SlotPool 存放由所有TM Offer 过来的slot 。Offer 的过程就是图-3中的3,4,5 或图-4中的3,4,5,6,7,8。当EG需要slot去执行给sub Task时, 它就从SlotPool里根据一定的策略poll 一个slot ,然后将SubTask打包 (这个在TM讲解中展开) 发送相应的TM 去执行 。 SlotPool实现了一个RPCEndPoint : SlotPoolGateway, 如图-5中所示,感觉这个Gatway是为TM OfferSlot准备的。 实际上TM调用的是JobMasterGateway (到JobMaster), 然后JobMaster 通过SlotPoolGateway这个RPC 接口与SlotPool通讯的。 看代码时看到SlotPoolGateway时比较奇怪的, 因为它作为JobMaster的组件,是没有必要实现为PCEndPoint的。集群中运行的每一个Job, 都会由一个JobMaster创建出来为之服务, 每一个JobMaster 都有一个SlotPool存放这个Job分配的Slot 。有一种可能是Slotpool的实现这打算将slotpool共享给所有的的JobMaster ? 如果那样的Slotpool  需要由Zookeepr 管理做Leader Selection 和 FailOver, 其实也没什么必要。
  • JobMasterGateway 是外界(ResourceManager, TaskManager)用来 同JobMaster通讯的RPC接口。
  • RMConnection 和TMConnection(多个)是JobMaster 同TM 和TM 通讯的PRC 通道。这些通道里包裹了RM和TM的PRCEndPoint的AKKA地址,以及永远RPC call 的 XXXXGateway接口。比如ResourceManagerGateway 和TaskManagerGateay。
  • HearbeatManager 会以Interval为(10,000 ms),timeout 为(50,000ms) 向TM和RM发送heartBeat, 如果timeout 发生则相应的ErrorHandling 会出发, 比如重新连接RM,切断timeout的TM 。interval 和 timeout都是可配置的, 前面的两个数值是缺省值。
  • FatalErrorHandler : 通常指向ClusterEntryPoint (回顾一下图-2)。JobMaster 在无法连接和注册有效的RM时会触发FatalErrorHandler的onFatalError方法。onFatalError通常会简单记下log, 然后推出JVM 。
  • RestartStrategy用于在EG中,但Job失败时,尝试重启Job, RestartStrategy 可以在Flink Java/Scala API种指定 。
  • BackPressureTracker,  当一个operator的处理速度小于的上游的下发速度, 数据就会在input buffer 里积压, 当buffer满了的情况, 数据就会无处可放。 Flink将这种情况称作为BackPressure 。Dispatch 会持续的通过JM的BackPressureTracker对每一个TM每个 sub Task做Stack trace(100 stack traces every 50ms , configurable) ,然后用可能有BP的stack trace (比如访问buffer, 访问网络栈等)同total tack trace 的比例决定系统是否有Back Pressure风险 。比如 <10%是OK的, <50%是低危的, >50是高危的。这个比率是可以在Flink WebMonitor的Metrics里看到的。如果是高危的怎么办, 实际上Flink就是把他通过Metrics发了出来,没有做任何handling , 目的是让用户手工在工作流种做相应调整, 比如加速和降速Datasource 的输出速率, 在某个operator 上加cache等。
  •  

 1.2.3  ExcutionGraph 

EG是面向Job 并行运行的图结构,在JobGraph的基础上它加入了对Operator并行执行的子任务,以及子任务的输入输出的描述 。 

                                                 图-6 Execution Graph

 

  • ExecutionJobVertex : 对于每个 Operator 或Task(单独的或chained Opertor) ,EG 都会创建一个ExecutionJobVertex(EJV)对应 。
  • ExecutionVertex: 对于它 的每一个并行子任务  (sub task), EVJ都会创建一个ExecutionVertex(EV)对应 。每一个EV都知道输出到哪里 (IntermediateResult), 到哪里获取input (ExecutionEdges : 底层数据也来自IRP ), 和执行的Operator类。
  • IntermediateResultPartition(IRP)  : 代表IntermediateResult(IR)的一个Partition 。 它描述了它是由哪个EJV提供数据,
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值