二、Spark通信机制
2.1 Spark通信机制的重要概念
(1)RpcEndpoint:RPC端点,Spark将每个通信实体/集群节点(Client/Master/Worker等)都称之为一个Rpc端点,且都实现了RpcEndpoint接口,内部根据不同端点的需求,设计不同的消息和不同的业务处理,如果需要发送消息则调用Dispatcher中的相关方法;
(2)RpcEnv:RPC上下文环境,RpcEndpoint运行时依赖的上下文环境称之为RpcEnv,其管理RpcEndpoint需要的一些东西,如Dispatcher消息分发器,每个RpcEndpoint都对应着自己的一个RpcEnv;
(3)Dispatcher(存在于RpcEnv中):消息分发器,用于发送消息或者从远程RpcEndpoint和本地接收消息,并将消息分发至对应的指令收件箱/发件箱。如果消息接收方是自己的话(即发往本地的消息或从远程RpcEndpoint接收到的消息),那就将消息存入本地RpcEndpoint的收件箱,如果消息接收者为远程RpcEndpoint,则将消息放入指定远程RpcEndpoint的发件箱,每个RpcEndpoint内部维护了一批远程RpcEndpoint的Outbox,表示本地RpcEndpoint需要与这些RpcEndpoint进行通信。Dispatcher类中的MessageLoop线程负责读取LinkedBlockingQueue中的RpcMessage消息,然后处理Inbox中的消息,由于是阻塞队列,当没有消息的时候自然阻塞,一旦有消息,就开始工作。Dispatcher的ThreadPool负责消费这些Message。也就是说某个RpcEndpoint需要发送消息或接收到消息时均先将消息发送到自己的Dispatcher消息分发器中,然后再由消息分区器来分配消息的处理方式,RpcEndpoint对应的Dispatcher中维护了一些远程RpcEndpoint的地址(以Outbox的形式)。
(4)Inbox:本地RpcEndpoint的消息收件箱,一个本地RpcEndpoint对应一个Inbox,Dispatcher每次向Inbox存入消息时,都将对应的EndpointData(内部维护该消息发往的本地端点的endpoint和endpointRef,以及本地端点对应的Inbox)加入到内部的Receiver Queue(阻塞队列)中,且在Dispatcher创建时会启动一个单独的线程轮询Receiver Queue队列查看有无消息,若有消息则进行消息消费。
(5)Outbox(存在于RpcEnv中):消息发件箱,一个RpcEndpoint对应的RpcEnv对象中维护了一些远程RpcEndpoint地址及其Outbox的映射(即某个RpcEndpoint中维护了一些远程RpcEndpoint的地址和对应于该远程RpcEndpoint的发件箱,便于拿到远程RpcEndpoint的地址,发送消息给远程RpcEndpoint),当消息放入Outbox后,紧接着将消息通过远程RpcEndpoint(实际是调用该端点的ref引用)对应的TransportClient发送出去。消息放入发件箱以及发送过程是在同一个线程中进行的。即发件箱中的消息是通过远程RpcEndpoint对应的TransportClient发送给其它RpcEndpoint。
(6)TransportClient:Netty通信客户端,根据Outbox消息的receiver的地址信息,实例化相应远程端点的TransportClient,然后发送消息至远程TransportServer,每个RpcEndpoint也维护了一个其它远程RpcEndpoint的TransportClient集合。
(7)TransportServer:Netty通信服务端,一个RpcEndpoint对应一个TransportServer,接收到远程消息后调用自己的Dispatcher分发消息至本地endpoint的收件箱。
综上所述:一个RpcEndpoint内部主要维护了以下一些东西:
(1)依赖一个RpcEnv上下文执行环境;
(2)维护一个Inbox,接收本地发往本地的消息以及从远程RpcEndpoint接收到的消息;
(3)维护多个远程RpcEndpoint对应的Outbox集合,每个远程RpcEndpoint对应一个Outbox,本地往某个远程RpcEndpoint发消息时,先根据远程RpcEndpoint地址找到对应的Outbox,往那个Outbox添加消息;
(4)一个Dispatcher消息分发器;
(5)维护远程RpcEndpoint对应的TransportClient映射;
(6)维护本地的TransportServer用于接收远程RpcEndpoint发送过来的消息。
RpcEndpoint接收/发送消息的过程大致如下:
接收消息:每个RpcEndpoint对应的TransportServer负责接收其它RpcEndpoint发送过来的消息,然后将消息添加到本地RpcEndpoint中的Dispatcher中,Dispatcher再将消息添加到本地的Inbox收件箱,Inbox中会有一个阻塞队列用于存放接收到的消息,然后有一个线程会不断地轮询这个队列,拉取消息进行消息的消费;
发送消息:RpcEndpoint发送消息时,先将消息放到自己的Dispatcher中,然后Dispatcher根据消息发往的远程RpcEndpoint地址找到相应的Outbox发件箱(每个本地端点维护了一些远程端点的Outbox对象),然后将消息添加到指定的Outbox中,后续通过远程RpcEndpoint对应的TransportClient将对应Outbox发件箱中的消息发送出去。
2.2 Spark通信相关类解析
2.2.1 RpcEndpoint
在Spark中,RpcEndpoint是所有通信实体的抽象。RpcEndpoint是一个trait,其中定义了一些函数,这些函数都是在收到某个特定的消息后才会被触发,执行相应的逻辑(真正的执行逻辑是由具体实现类实现的,如Master),其中onStart、receive和onStop这三个方法的调用是有先后顺序的,RpcEndpoint中的方法如下:
rpcEnv:RpcEndpoint执行依赖的上下文环境;
receive:接收消息并处理;
receiveAndReply:接收消息处理后,并给消息发送者返回响应;
onError:发生异常时调用;
onConnected:当客户端(远程端点)与本地RpcEndpoint建立连接后调用;
onDisconnected:当客户端与本地RpcEndpoint失去连接后调用;
onNetworkError:当网络连接发生错误时调用;
onStart:RpcEndpoint启动时调用;
onStop:RpcEndpoint停止时调用。
RpcEndpoint中最重要的几个方法:onStart、receive、receiveAndReply以及onStop,这几个方法就是RpcEndpoint的生命周期。
RpcEndpoint的继承体系如下图:
由上图可知,Master和Worker等都是一个RpcEndpoint,ClientEndpoint是每个SparkApp的终端点——即Spark应用对应的RpcEndpoint,DriverEndpoint是Spark Driver的endpoint,HeartbeatReceiver是Executor发送心跳消息给Driver的Endpoint,CoarseGrainedExecutorBackend是Spark Executor的endpoint,用于执行Executor的相关操作。上述这些RpcEndpoint都继承自ThreadSafeRpcEndpoint,即均是线程安全的。
2.2.2 RpcEndpointRef
RpcEndpointRef是对RpcEndpoint的引用,本地RpcEndpoint要向远端的一个RpcEndpoint发送消息时,必须通过远程RpcEndpoint的引用 RpcEndpointRef才能往远程端点发送消息。RpcEndpointRef指定了ip和port,是一个类似spark://host:port这种的地址,RpcEndpointRef在Spark中只有一个子类,即NettyRpcEndpointRef,即无论是何种类型的RpcEndpoint,其ref引用都是NettyRpcEndpointRef对象,内部提供了一些方法用于发送消息,如下所示:
RpcEndpoint的地址在spark中表示为RpcAddress对象,该类只有两个字段:host和port
//每个RpcEndpoint均对应不同的port,所以一个RpcEndpoint的地址由host和port唯一确定 private[spark] case class RpcAddress(host: String, port: Int) { def hostPort: String = host + ":" + port def toSparkURL: String = "spark://" + hostPort override def toString: String = hostPort }
其中的address返回该引用对应的真实RpcEndpoint的地址,name则返回对应真实RpcEndpoint的名称。此外该类还提供了ask和askSync方法,其中ask方法是异步的,返回一个Future对象用于获取响应,而askSync则是同步的,调用时会阻塞等待结果返回,而send()方法只管发送消息,不关心响应,endpointRef中的ask和send方法都是用于向远程RpcEndpoint发送消息的。
2.2.3 RpcEnv
RpcEnv是RpcEndpoint的运行环境,内部维护了RpcEndpoint运行所需的一系列东西,如Dispatcher消息分发器、Outbox发件箱等(每个远程端点都对应一个Outbox),内部结构如下图:
RpcEnv类有一个伴生对象RpcEnv,该伴生对象内提供了两个create方法用于创建RpcEnv对象,其实最终调用的都是第二个create方法,通过RpcEnvFactory工厂创建NettyRpcEnv对象。
RpcEnv类中提供了一些方法用于在Dispatch