深入flink系列——集群RPC通信机制

前言

Flink集群中的各个组件(JobMaster、TaskManager、Dispatcher等)之间的RPC通信框架是基于Akka实现的,本文主要学习Flink中的RPC网络通信框架的实现原理,以及集群运行时组件之间相互访问和通信的过程。

觉得文章有收获,欢迎关注公众号鼓励一下作者呀~
在学习的过程中,也搜集了一些量化、技术的视频及书籍资源,欢迎大家关注公众号【亚里随笔】获取
百度网盘资源

1 Akka介绍

1.1 Akka基本知识

Akka是Actor模型的一种实现,能够构建可扩展、弹性、快速响应的应用程序。Actor模型是一个并行计算模型,它把Actor作为并行计算的基本元素,Actor能够自己做出一些决策,如创建更多的Actor、发送更多的消息或者确定如何响应接收到的下一个消息。
Actor由状态(state)、行为(behavior)和邮箱(mailbox)组成,Actor之间可以通过交换消息的方式进行通信,提供了异步非阻塞、高性能的事件驱动编程模型。

  • 状态:Actor对象的变量信息,由Actor自己管理,避免了并发环境下的锁和内存原子性等问题。
  • 行为:指定Actor中的计算逻辑,通过接收到消息改变Actor的状态。
  • 邮箱:每个Actor都有自己的邮箱,通过邮箱能简化锁及线程管理。Actor之间通过邮箱进行通信,邮箱内部通过FIFO队列存储Actor发送的消息,Actor接收方从邮箱队列中获取消息并处理。


图1 Actor系统示意图

Akka系统的核心是ActorSystem和Actor。构建一个Akka系统,首先需要创建ActorSystem,再通过ActorSystem.actorOf和ActorContext.actorOf等接口创建Actor。另外,与Actor进行通信也只能通过ActorRef来进行。ActorRef对原生Actor实例进行了封装,外界不能随意修改其内部状态。

/*
程序运行结果:
hello!jack
hi! mary
*/
public class AkkaTest {
    private static ActorSystem actorSystem = null;
    private ActorRef helloActor = null;
    private ActorRef hiActor = null;

    @BeforeClass()
    public static void setup() {
        //构建ActorSystem
        actorSystem = AkkaUtils.createDefaultActorSystem();
    }

    @Before
    public void init() {
        //构建Actor,获取该Actor的引用,即ActorRef
        helloActor = actorSystem.actorOf(Props.create(HellowActor.class), "helloActor");
        hiActor = actorSystem.actorOf(Props.create(HiActor.class), "hiActor");
    }

    @AfterClass
    public static void teardown() throws Exception {
        //关闭系统
        actorSystem.terminate();
    }

    @Test
    public void testSay() {
        //通过hiActor给helloActor发送消息
        helloActor.tell("jack", hiActor);
    }


    public static class HellowActor extends AbstractActor {

        public Receive createReceive() {
            //根据消息类型路由处理方法
            return ReceiveBuilder.create()
                    .matchAny(this::handleMessage)
                    .build();
        }

        private void handleMessage(Object message) {
            //处理方法
            System.out.println("hello!" + message);
            //给发送者回信
            getSender().tell("mary", this.getSelf());
        }
    }

    public static class HiActor extends AbstractActor {

        public Receive createReceive() {
            return ReceiveBuilder.create()
                    .matchAny(this::handleMessage)
                    .build();
        }

        private void handleMessage(Object message) {
            System.out.println("hi! " + message);
        }
    }
}

1.2 flink RPC框架与Akka的关系

  • flink的RPC服务组件是RpcEndpoint,每个RpcEndpoint包含一个内置的RpcServer负责执行本地和远程的代码请求,RpcServer对应Akka中的Actor实例。RpcService的主要实现是AkkaRpcService,负责创建和启动RpcServer。
  • AkkaRpcService对应Akka中的ActorSystem,通过AkkaRpcService可以创建RpcEndpoint中的RpcServer。
  • AkkaRpcService的connect方法负责与远程RpcServer建立RPC连接,提供远程调用能力。
  • AkkaRpcActor负责Rpc的具体执行。


Flink RPC节点调用关系图
集群运行时中实现了RPC通信节点功能的主要有Dispatcher、ResourceManager、TaskManager以及JobMaster等组件。这些组件基于RPC通信,共同参与任务提交及运行的整个流程,如:通过客户端向Dispatcher提交JobGraph,JobManager向TaskManager提交Task请求,以及TaskManager向JobManager更新Task执行状态。

2 Flink RPC框架的构成

2.1 概览

Flink的RPC通信框架主要由几个重要角色构成的:RpcGateway、RpcEndpoint、RpcService、RpcServer(AkkaInvocationHandler)、AkkaRpcActor。

2.2 RpcGateway

  • RpcGateway主要定义RPC协议的通信行为,与远端Actor通信必须提供的地址。
  • RpcGateway用于远程调用RpcEndpoint的方法,可以理解为是对方的客户端代理。比如TaskExecutor向ResourceManager注册,会调用ResourceManagerGateway#registerTaskExecutor方法,ResourceManagerGateway就是ResourceManager的代理。
  • 集群的所有组件都有对应的RpcGateway接口,并且每个组件都会implements对应的接口,这样根据RpcGateway中暴露的方法签名就能调用到具体组件的实现上。

2.3 RpcEndpoint -> TaskExecutor…

  • RpcEndpoid是通信终端,是所有通信组件的父类,提供RPC服务组件的生命周期管理(start、stop)。
  • 每个RpcEndpoint都对应了一个路径(endpoitId和actorSystem),每个RpcEndpoint对应一个AkkaActor。
  • 所有需要实现RPC通信的集群组件都会继承RpcEndpoint,例如TaskExecutor、Dispatcher、ResourceManager,以及根据JobGraph动态创建和启动的JobMaster服务。
  • RpcEndpoint实现了RpcGateway接口,所有本地和远程的RpcGateway执行请求都会通过动态代理的形式转发到AkkaInvocationHandler代理类中执行。
  • RpcEndpoint在初始化时会调用rpcService.startServer,startServer会为当前的RpcEndpoint registerAkkaRpcActor,这样,当前的RpcEndpoint就有RpcActor处理消息了。后续当前RpcEndpoint的Rpc消息的接收与处理都是由AkkaRpcActor承接的。

2.4 RpcService -> AkkaRpcService

RpcService是RpcEndpoint的管理服务,类似于一个ActorSystem,它的主要作用:

  • 创建和启动RpcEndpoint的RpcServer(Actor)
  • 根据提供的地址连接到(对方的)RpcServer,并返回RpcGateway给调用客户端
  • 延迟/立刻调度Runnable、Callable

RpcServicer的实现类是AkkaRpcService,它是对ActorSystem的封装,基本可以理解为ActorSystem的一个适配器,其中维护了ActorRef到RpcEndpoint的映射关系。

RpcService在ClusterEntrypoint(JobMaster)和TaskManagerRunner(TaskExecutor)启动的过程中初始化并启动,也就是在启动集群的时候会提前创建好,需要rpcService的地方会将其透传过去,后续创建的各种RpcEndpoint也会以Map<ActorRef, RpcEndpoint>的形式管理在RpcService中。
TaskManager中的RpcService创建及启动过程:

RpcEndpoint的start()方法会继续调用rpcServer.start()
向RpcEndpoint本地的actor发送ControlMessages.START消息
本地的AkkaRpcActor的createRecive()方法会对接收的消息进行类型匹配
调用handleControlMessage将actor的状态转换为START

2.5 RpcServer -> AkkaInvocationHandler

RpcServer是自网关,是自身的代理,负责接收响应远端RPC消息请求,所有本地和远程的RpcGateway执行请求都会通过动态代理的形式转发到AkkaInvocationHandler代理类中执行。它的实现有两个:

  • AkkaInvocationHandler
  • FencedAkkaInvocationHandler

RpcServcer的启动是通知AkkaRpcActor切换为Start状态,能够开始处理远程调用。

2.6 AkkaRpcActor

是Akka Actor的具体实现,主要负责处理消息:

  • 本地Rpc调用的LocalRpcInvocation
    • 会指派给RpcEndpoint进行处理,如果有响应,
  • RunAsync & CallAsync
    • 这类消息带有可执行的代码,能直接在Actor线程中运行
  • 控制消息ControlMessage
    • 用于控制Actor,Start启动,Stop停止,停止后接收的消息会丢掉

AkkaRpcActor是RpcEndpoint初始化时,在rpcService.startServer()方法中调用registerAkkaRpcActor注册到actor中的。这样被调用方收到RpcInvocation消息时,会由调用方AkkaRpcActor的createRecive()方法会对接收的消息进行类型匹配。消息的传递与接收这一过程是由Akka系统完成的。

2.7 RPC交互过程

RPC交互过程分为请求和响应。
请求阶段:
在RpcService中调用connect方法与对端的RpcEndpoint(RpcServer)建立连接,connect根据给的地址返回InvocationHandler,也就是对方的代理。代理对象会调用AkkaInvocationHandler的invoke方法并传入RPC调用的方法和参数。
此时的AkkaInvocationHandler是在调用方的这一端创建的,使用的是被调用方的actorRef。这样一来,后续的AkkaInvocationHandler中进行invokeRpc时,就可以对被调用的actorRef进行tell及ask操作了。
响应阶段:
RPC消息是由调用方在connect时创建的被调用方的AkkaInvocationHandler代理向被调用方RpcEndpoint所绑定的Actor的ActorRef发送的。AkkaRpcActor是接收的入口,而AkkaRpcActor在RpcEndpoint中注册生成,负责将消息交给不同的方法处理。

3 集群组件之间的RPC通信

这一部分我们通过具体的实例来梳理集群运行时中各组件是如何基于RPC通信框架相互调用的。
当TaskExecutor启动后,会向ResourceManager注册当前TaskManager的信息。这种注册操作就是在构建TaskManager到ResourceManager之间的RPC连接,注册在Flink中被抽象为RegisteredRpcConnection。
集群组件之间的RPC通信都是通过创建RegisteredRpcConnection进行的,各组件之间的注册连接主要通过RegisteredRpcConnection基本类提供的,其实现子类有:

  • JobManagerRegisteredRpcConnection:用于TaskManager与JobManager之间的RPC连接
  • ResourceManagerConnection:用于JobManager与ResourceManager之间的RPC连接
  • TaskExecutorToResourceManagerConnection:用于TaskExecutor与ResourceManager之间的RPC连接


上图中:

  • RegisteredRpcConnection的generateRegistration()抽象方法主要用于生成组件之间的RPC连接,每次调用RegisteredRpcConnection.start()时,都会调用generateRegistration()创建新的相应类型的RetryingRegistration。
  • RetryingRegistration的startRegistration()方法会使用RpcService连接指定的RpcEndpoint地址,底层调用的是rpcService.connect()方法,返回RpcGateway的代理对象。这样一来,当前组件就能通过RpcGateway连接到目标Endpoint上了。
  • RetryingRegristration的invokeRegistration()抽象方法,用于实现子类的RPC注册操作。例如,ResourceManagerRegistration中会实现invokeRegistration(), 在方法中调用resourceManager.registerTaskExecutor()将TaskExecutor注册到ResourceManger。这里的resourceManager就是ResourceManagerGateway。

3.1 TaskManager向ResourceManager注册

TaskManager向ResourceManager的注册过程如下图所示:

  1. TaskExecutor启动会,RPC框架会回调TaskExecutor#onStart()方法
  2. TaskExecutor调用startTaskExecutorServices(),启动TaskExecutor的内部组件服务
  3. startTaskExecutorServices方法会调用resourceManagerLeaderRetriever#start()方法,启动ResourceManager组件高可用领导节点监听服务,并传入ResourceManagerLeaderListener,用于监听ResourceManager Leader节点的变化
  4. 当ResourceManagerLeaderListener接收到来自ResourceManager的leaderAddress和leaderSessionID后,调用notifyOfNewResourceManagerLeader()方法通知TaskExecutor和新的ResourceManagerLeader建立RPC连接
  5. 调用TaskExecutor的reconnectToResourceManager、tryConnectToResourceManager及connectToResourceManager,创建与ResourceManager组件之间的RPC连接
  6. 创建TaskExecutorToResourceManagerConnection实例,并通过TaskExecutorToResourceManagerConnection.start()方法启动RPC网络连接
  7. start RegisteredRpcConnection时,会调用createNewRegistration()创建新的RetryingRegistration
  8. 在createNewRegistration中会调用子类TaskExecutorToResourceManagerConnection中实现的generateRegistration()方法,创建与ResourceManager之间的RPC连接
  9. 调用RetryingRegistration.startRegistration()方法注册具体的RPC连接
  10. 其中,会调用rpcService.connect()方法创建并获取ResourceManager对应的RpcGateway
  11. 获取到ResourceManagerRpcGateway之后,会调用register()内部方法
  12. 其中,会调用ResourceManagerRegistration子类的invokeRegistration()实现
  13. 在 invokeRegistration的实现中,会调用ResourceManagerGateway.registerTaskExecutor(),经过RPC调用最终向ResourceManager注册TaskExecutor。


3.2 JobMaster向ResourceManager申请slot计算资源

JobMaster向ResourceManager的注册过程与TaskManager向ResourceManager的注册过程上是相同的,这里我们主要关注JobMaster向ResourceManager申请slot的过程。
当JobMaster组件启动以后,在onStart()方法中会立即调用JobMaster.startJobMasterServices()方法启动JobMaster的内部服务,其中有SlotPool组件。在JobMaster和ResourceManager之间的ResourceManagerConnection创建成功后,会调用onRegistrationSuccess()方法执行RPC连接完成之后的操作,包括调用在slotPoolService.connectToResourceManager的过程中向ResourceManager发送申请Slot计算资源的请求。
JobMaster向ResourceManager申请slot的主要过程如下图:

4 小结

总结起来,从集群组件间调用的层面来看,RpcEndpoint组件会创建与其他RpcEndpoint之间的RegisteredRpcConnection,并通过RpcGateway接口的动态代理类与其他组件进行远程调用通信,而底层通信则依赖Akka通信框架实现的。
至此,Flink RPC集群组件之间的RPC通信的专题学习就告一段落了,总算梳理了个大概,flink源码越看越觉得博大精深,有很多值得我学习的地方。鄙人才疏学浅,可能有疏漏之处,也可能有些地方梳理的不对,请多交流指教。

学习资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值