概述
这篇文章侧重于分析JobManager和TaskManager的启动过程以及注册,还有Flink的implementation中所用到的设计模式。本文从本地与standalone模式进行解析。
Akka 简介
- 因为组件之间的信息传递是通过Akka工具包,所以在这儿我做一个简单的解释
-
首先参考Akka官方文档给出的一个抽象的图
- 如图就是对ActorSystem的一个高度抽象,所有的Actor成树状,user actor的子孙就是用户创建的Actor,system下面是Akka的监管与支持的Actor,往往不需要用户过多参与
- Actor之间如果持有对方的ActorRef则可以向对方发送消息
- 父Actor负责监管子Actor抛出的异常能被父Actor处理,则父Actor可以重启或者废弃它,如果不能处理,会继续向上抛异常。一个Actor如果推出,那么它的所有子Actor都会退出。
- 它们共同组成了一个ActorSystem
Flink的架构
-
首先上一张从Flink官方文档拿来的架构图
这次我会从JobManager和TaskManager着手来解析Flink的启动过程
Flink中JobManager与TaskManager,JobManager与Client的交互是基于Akka工具包的,是通过消息驱动,这样就把中心放到了消息的接收,发送与处理,而且由于对每个Akka中的Actor来说,消息是同步的,排在一个队列中,极大地简化了多线程程序设计的复杂度,关于Akka的一些学习资料我会放在文章最后
-
下面的启动过程我会分为入口,ActorSystem的创建,JobManager的启动,TaskManager的启动和注册这几块来讲
入口
如果看过该系列上一篇文章,应该知道怎么找到在Local和Standalone模式下程序的入口
这里我直接给出来,就是org.apache.flink.runtime.jobmanager 中的main方法
-
main方法主要可以分为,一是启动环境的准备
-
二是处理输入的命令行参数,并准备好包含配置信息的Configuration对象,以及host地址与端口号,还有运行模式
-
三是新建一个Callable对象,在另一个线程上运行runJobManager方法
-
runJobManager有 个重载方法,第一个方法中绑定了端口号并建立了socket链接,并调用第二个重载方法
-
第二个重载方法根据本机硬件情况建立futureExecutor(对Future类不了解的话可以简单理解成Java中带返回值的Runnable,细节不做讨论), ioExecutor,并调用startActorSystemAndJobManagerActors方法建立ActorSystem并等待结束
ActorSystem创建
- 在这里Flink做了一个Akka的工具类来简化逻辑,本质上就是从Configuration配置文件类中提取Akka启动所需配置信息,并根据配置建立ActorSystem,ActorSystem可以是本机的,也可以是跨多台机器的,具体逻辑都在AkkaUtils中,有兴趣可以研究
建立JobManager的可视化WebMonitor,这里不做介绍
-
重点出现,在这里通过ActorSystem建立了JobManager的Actor,并建立了一个JobManager process reaper来做简单的失败检测
-
(*)如果实在Local模式下的话,则启动一个内嵌的TaskManager,如果不是,则需要在另一个JVM中启动TaskManager,通过taskmanager.sh脚本来完成
针对每一个TaskManager Actor再启动一个WebMonitor可视化界面
JobManager
- 对于JobManager与TaskManager的启动与关闭,会有三个环节,preStart,handleMessage与postStop
- 对于每一个Actor,必须Override抽象方法handleMessage,也就是Actor针对收到的message做的业务逻辑,可以选择Override preStart和postStop方法,做一些启动前准备与结束时的清理
- (关于Scala的多重继承,以及菱形继承的问题我这边不做过多讨论,网上有一些帖子,也可以通过阅读Scala In Depth来获得更深入了解,这对Java程序员来说是一个新的概念)
- 首先是启动前准备preStart
-
启动leaderElectionService,将JobManager本身作为参数传入
这边值得一提的是设计模式,策略模式与观察者模式,因为leaderElectionService是一个Java的接口,在生产环境中有非高可用(单点失败)的Implementation与基于Zookeeper的高可用模式,可以再运行时更改该接口的行为。JobManager还实现了LeaderContender接口,实现了多个CallBack方法,当leaderElectionService被修改时,会通知JobManager来调整,典型的观察者模式,适用于在高可用模式下作为Leader的JobManager被更改的情况。
- 接着是启动SubmittedJobGraph服务,失败恢复服务与Metrics,这些会在以后讲到
-
在这儿调用了leaderElectionService,对高可用模式的解析可能会在以后补上,现在侧重于Standalone模式下的解析,在StandaloneLeaderElectionService中,因为只有一个JobManager,所以直接在start时调用LeaderContentder中的callback方法,也就是JobManager的grantLeadership方法
- 在该方法中向JobManager Actor本身发送了一条消息,从而在handleMessage中进行接收处理(!在Scala的Akka包中是发送消息的方法)
-
- handleMessage(启动相关)
-
根据消息的类型进行匹配,当接收到GrantLeadership的Message后,会匹配到如下代码
首先确认身份,再判断是否为高可用模式,如果是高可用模式还需要发送恢复任务的消息,如果不是,JobManager的启动已完成
-
TaskManager
- TaskManager Actor创建
- 如果为本地模式,则直接调用startTaskManagerComponentAndActor方法,如果是用脚本启动,则会进入TaskManager的main函数
- 对于standalone模式
-
在main函数中解析完命令行参数并生成配置文件对象后,会生成resourceID作为身份
-
接下来会新建一个Callable对象并调用selectNetworkInterfaceAndRunTaskManager方法
-
在selectNetworkInterfaceAndRunTaskManager方法中,先绑定地址与端口号,建立远程ActorSystem,然后调用runTaskManager方法
-
在runTaskManager方法中通过调用startTaskManagerComponentsAndActor并传入远程ActorSystem,至此与local模式的启动一致,区别在于ActorSystem是与JobManager一致还是远程另外一个ActorSystem,但对于开发者来说对Actor之间的消息传递方法并没有任何区别
-
-
在startTaskManagerComponentsAndActor创建ioManager,network,leaderRetrievalService等创建TaskManager所需要的参数,通过actorSystem来创建TaskManager Actor
- prestart
-
首先启动leaderRetrievalService,和LeaderElection一样使用了策略模式来处理是否高可用两种情况,观察者模式来接收对象变化并调用callback方法
-
这边同样关注Standalone版本的Implementation,在start中调用TaskManager的notifyLeaderAddress回调方法,并将jobManager地址作为参数传入
-
在TaskManager实现的notifyLeaderAddress方法中发送JobManagerLeaderAddress消息给自己
-
- handleMessage(启动相关)
- TaskManager Actor一旦接收到该Message要不就是刚启动,要不就是JobManager的Leader发生了改变,调用handleJobManagerLeaderAddress函数
-
在handleJobManagerLeaderAddress函数中,先断开连接,然后出发TaskManager的注册操作
- TaskManager Actor一旦接收到该Message要不就是刚启动,要不就是JobManager的Leader发生了改变,调用handleJobManagerLeaderAddress函数
TaskManager的注册
- 在TaskManager的注册中,设计了与JobManager的消息交互,所以单独分开来讲
- TaskManager中的发送注册请求
- 在handleJobManagerLeaderAddress中触发了triggerTaskManagerRegistration注册函数
-
在该函数中,提取超时信息设置,以及当前尝试的ID,清空已经在调度器中应该被废弃的注册消息,并向自身发送尝试次数为第一次的TriggerTaskManagerRegistration消息
-
因为TriggerTaskManagerRegistration是在TaskManager Actor接收到RegistrationMessage的子类,所以在接收到该消息时,根据RegistrationMessage来匹配,并调用handleRegistrationMessage方法
-
匹配到TriggerTaskManagerRegistration消息后,先判断该消息有没有失效,如果没有,则有三种情况
- 如果已连接成功,写入日志
- 如果超时,写入日志并推出
- 除此之外,进行下一次尝试,向JobManager Actor发送RegisterTaskManager消息,并在调度其中注册下一次TriggerTaskManagerRegistration的消息的发送,知道出现第一种情况注册成功或第二种情况注册失败为止
-
JobManager接收到注册请求消息
- 根据消息找到相应的TaskManager地址
- 一旦JobManager接收到RegisterTaskManager消息,先想ResourceManager注册(这边不做介绍,这边的?是Akka里面发送ask消息并期望得到一个返回值),如果Resource注册失败则向发送ReconnectResourceManager消息进行重试
- 如果该TaskManager已经注册在instanceManager中,则发送AlreadyRegistered消息给相应的TaskManager
- 如果还未注册,则向instanceManager注册该TaskManager,并发送AcknowledgeRegistration给相应的TaskManager
- 出现异常则拒绝注册,发送RefuseRegistration消息
- TaskManager接到返回消息
-
AcknowledgeRegistration注册成功,如果isConnected为true则是已连接,判断该消息是否由当前链接的JobManager发送并写入日志,如果未连接,调用associateWithJobManager进行接收消息钱的准备工作(后续会深入解析)
- AlreadyRegistered重复注册,写入日志,逻辑与 AcknowledgeRegistration消息的处理相同
- RefuseRegistration注册失败,如果JobManager地址存在,则发送新的TriggerTaskManagerRegistration, 重复到TaskManager注册部分的福州,如果没有地址,则验证如果发送消息的JobManager是否是当前已连接的JobManager写入日志,对结果没有影响
-
总结
至此,JobManager和TaskManager的启动过程以及TaskManager的注册过程解析已经完成。解析中没有办法做到面面俱到,把自己觉得重要的点挑了出来,主要是能再时间上形成一个线,方便理解。
下面还有一个根据时间线来做的思维导图,侧重于Local模式下的启动,虚线代表调用或者是消息。
附录
Akka学习资料:
- Akka 官方文档 link
- Akka 手册翻译 link 翻译有一些晦涩
- Akka 系列博客 link