Spark RPC模块源码学习

1,905 阅读10分钟

其他更多java基础文章:
java基础学习(目录)


学习资料
spark 源码分析之十二--Spark RPC剖析之Spark RPC总结 **
Spark2.0.2源码分析——RPC 通信机制(消息处理)
spark网络通信-RPC的实现
Spark 消息通信架构
深入解析Spark中的RPC **
Spark 底层网络模块
Spark RPC 通信机制
Spark2.1.0——内置RPC框架详解 **
Spark RPC实现原理分析
Spark RPC之Dispatcher、Inbox、Outbox

能力有限,目前还是个学习者的姿态,所以只是记录一下rpc模块源码的学习过程。在学习的过程中发现上面几个是不错的学习资料,推荐给大家,带*号表示值得优先查看学习的资料。

因为每个资料都各有侧重点,所以可能在看的时候对一些没有细讲的类和架构不了解。下面我对上面的资料进行了总结,可以在学习的过程中对照的来看。

重要概念

  • RpcEnv:RpcEnv 抽象类表示一个 RPC Environment,管理着整个RpcEndpoint的生命周期.每个 Rpc 端点运行时依赖的环境称之为 RpcEnv。

  • NettyRpcEnv: RpcEnv的唯一实现类

  • RpcEndpoint:RPC 端点 ,Spark 将每个通信实体都都称之一个Rpc端点,且都实现 RpcEndpoint 接口,比如DriverEndpoint,MasterEndpont,内部根据不同端点的需求,设计不同的消息和不同的业务处理。

  • Dispatcher:消息分发器(来自netty的概念),负责将 RpcMessage 分发至对应的 RpcEndpoint。Dispatcher 中包含一个 MessageLoop,它读取 LinkedBlockingQueue 中的投递 RpcMessage,根据客户端指定的 Endpoint 标识,找到 Endpoint 的 Inbox,然后投递进去,由于是阻塞队列,当没有消息的时候自然阻塞,一旦有消息,就开始工作。Dispatcher 的 ThreadPool 负责消费这些 Message。

  • EndpointData:每个endpoint都有一个对应的EndpointData,EndpointData内部包含了RpcEndpoint、NettyRpcEndpointRef信息,与一个Inbox,收信箱Inbox内部有一个InboxMessage链表,发送到该endpoint的消息,就是添加到该链表,同时将整个EndpointData添加Dispatcher到阻塞队列receivers中,由Dispatcher线程异步处理

  • Inbox:一个本地端点对应一个收件箱,Inbox 里面有一个 InboxMessage 的链表,InboxMessage 有很多子类,可以是远程调用过来的 RpcMessage,可以是远程调用过来的 fire-and-forget 的单向消息 OneWayMessage,还可以是各种服务启动,链路建立断开等 Message,这些 Message 都会在 Inbox 内部的方法内做模式匹配,调用相应的 RpcEndpoint 的函数。

  • RpcEndPointRef: RpcEndpointRef是一个对RpcEndpoint的远程引用对象,通过它可以向远程的RpcEndpoint端发送消息以进行通信。

  • NettyRpcEndpointRef:RpcEndpointRef 的唯一实现类,RpcEndpointRef的NettyRpcEnv版本。此类的行为取决于它的创建位置。在“拥有”RpcEndpoint的节点上,它是RpcEndpointAddress实例的简单包装器。

  • RpcEndpointAddress:主要包含了 RpcAddress (host和port) 和 rpc endpoint name的信息

  • Outbox:一个远程端点对应一个发件箱,NettyRpcEnv 中包含一个 ConcurrentHashMap[RpcAddress, Outbox]。当消息放入 Outbox 后,紧接着将消息通过 TransportClient 发送出去。

  • TransportContext:是一个创建TransportServer, TransportClientFactory,使用TransportChannelHandler建立netty channel pipeline的上下文,这也是它的三个主要功能。TransportClient 提供了两种通信协议:控制层面的RPC以及数据层面的 "chunk抓取"。用户通过构造方法传入的 rpcHandler 负责处理RPC 请求。并且 rpcHandler 负责设置流,这些流可以使用零拷贝IO以数据块的形式流式传输。TransportServer 和 TransportClientFactory 都为每一个channel创建一个 TransportChannelHandler对象。每一个TransportChannelHandler 包含一个 TransportClient,这使服务器进程能够在现有通道上将消息发送回客户端。

  • TransportServer:TransportServer是RPC框架的服务端,可提供高效的、低级别的流服务。

  • TransportServerBootstrap:定义了服务端引导程序的规范,服务端引导程序旨在当客户端与服务端建立连接之后,在服务端持有的客户端管道上执行的引导程序。用于初始化TransportServer

  • TransportClientFactory:创建传输客户端(TransportClient)的传输客户端工厂类。

  • TransportClient:RPC框架的客户端,用于获取预先协商好的流中的连续块。TransportClient旨在允许有效传输大量数据,这些数据将被拆分成几百KB到几MB的块。简言之,可以认为TransportClient就是Spark Rpc 最底层的基础客户端类。主要用于向server端发送rpc 请求和从server 端获取流的chunk块。

  • TransportClientBootstrap:是在TransportClient上执行的客户端引导程序,主要对连接建立时进行一些初始化的准备(例如验证、加密)。TransportClientBootstrap所作的操作往往是昂贵的,好在建立的连接可以重用。用于初始化TransportClient

  • TransportChannelHandler:传输层的handler,负责委托请求给TransportRequestHandler,委托响应给TransportResponseHandler。在传输层中创建的所有通道都是双向的。当客户端使用RequestMessage启动Netty通道(由服务器的RequestHandler处理)时,服务器将生成ResponseMessage(由客户端的ResponseHandler处理)。但是,服务器也会在同一个Channel上获取句柄,因此它可能会开始向客户端发送RequestMessages。这意味着客户端还需要一个RequestHandler,而Server需要一个ResponseHandler,用于客户端对服务器请求的响应。此类还处理来自io.netty.handler.timeout.IdleStateHandler的超时。如果存在未完成的提取或RPC请求但是至少在“requestTimeoutMs”上没有通道上的流量,我们认为连接超时。请注意,这是双工流量;如果客户端不断发送但是没有响应,我们将不会超时。

    当TransportChannelHandler读取到的request是RequestMessage类型时,则将此消息的处理进一步交给TransportRequestHandler,当request是ResponseMessage时,则将此消息的处理进一步交给TransportResponseHandler。

  • TransportResponseHandler:用于处理服务端的响应,并且对发出请求的客户端进行响应的处理程序。

  • TransportRequestHandler:用于处理客户端的请求并在写完块数据后返回的处理程序。

  • MessageEncoder:在将消息放入管道前,先对消息内容进行编码,防止管道另一端读取时丢包和解析错误。

  • MessageDecoder:对从管道中读取的ByteBuf进行解析,防止丢包和解析错误;

  • TransportFrameDecoder:对从管道中读取的ByteBuf按照数据帧进行解析;

  • StreamManager:处理ChunkFetchRequest和StreamRequest请求

  • RpcHandler:处理RpcRequest和OneWayMessage请求

  • Message:Message是消息的抽象接口,消息实现类都直接或间接的实现了RequestMessage或ResponseMessage接口,其中RequestMessage的具体实现有四种,分别是:

    • ChunkFetchRequest:请求获取流的单个块的序列。ChunkFetch消息用于抽象所有spark中涉及到数据拉取操作时需要传输的消息
    • RpcRequest:此消息类型由远程的RPC服务端进行处理,是一种需要服务端向客户端回复的RPC请求信息类型。
    • OneWayMessage:此消息也需要由远程的RPC服务端进行处理,与RpcRequest不同的是不需要服务端向客户端回复。
    • StreamRequest:此消息表示向远程的服务发起请求,以获取流式数据。Stream消息主要用于driver到executor传输jar、file文件等。

    由于OneWayMessage 不需要响应,所以ResponseMessage的对于成功或失败状态的实现各有三种,分别是:

    • ChunkFetchSuccess:处理ChunkFetchRequest成功后返回的消息;
    • ChunkFetchFailure:处理ChunkFetchRequest失败后返回的消息;
    • RpcResponse:处理RpcRequest成功后返回的消息;
    • RpcFailure:处理RpcRequest失败后返回的消息;
    • StreamResponse:处理StreamRequest成功后返回的消息;
    • StreamFailure:处理StreamRequest失败后返回的消息;

重要图例

spark rpc 整体架构图

spark rpc 整体架构图

上图的是否为本地ref的判断应该是画反了,RpcEndpointRef可以是本地的RpcEndpoint的简单包装也可以是远程RpcEndpoint 的代表。当RpcEndpoint 发送给 RpcEndpointRef 时,如果这个 RpcEndpointRef 是本地 RpcEndpointRef,则事件消息会被Dispatcher做进一步分发。如果是远程消息,则事件会被进一步封装成OutboxMessage,进而通过本地TransportClient将这个消息通过channel 发送给远程的 RpcEndpoint。

NettyRpcEnv结构图

NettyRpcEnv结构图

Spark内置RPC框架的基本架构

Spark内置RPC框架的基本架构 TransportContext内部包含传输上下文的配置信息TransportConf和对客户端请求消息进行处理的RpcHandler。TransportConf在创建TransportClientFactory和TransportServer时都是必须的,而RpcHandler只用于创建TransportServer。TransportClientFactory是RPC客户端的工厂类。TransportServer是RPC服务端的实现。图中记号的含义如下:

记号①: 表示通过调用TransportContext的createClientFactory方法创建传输客户端工厂TransportClientFactory的实例。在构造TransportClientFactory的实例时,还会传递客户端引导程序TransportClientBootstrap的列表。此外,TransportClientFactory内部还存在针对每个Socket地址的连接池ClientPool。
ClientPool实际是由TransportClient的数组构成,而locks数组中的Object与clients数组中的TransportClient按照数组索引一一对应,通过对每个TransportClient分别采用不同的锁,降低并发情况下线程间对锁的争用,进而减少阻塞,提高并发度。

记号②: 表示通过调用TransportContext的createServer方法创建传输服务端TransportServer的实例。在构造TransportServer的实例时,需要传递TransportContext、host、port、RpcHandler以及服务端引导程序TransportServerBootstrap的列表

管道处理请求图

管道处理请求图

RPC框架服务端处理请求、响应流程图

RPC框架服务端处理请求、响应流程图

客户端请求、响应流程图

客户端请求、响应流程图

Spark Message图例

Spark Message图例

Message是消息的抽象接口,消息实现类都直接或间接的实现了RequestMessage或ResponseMessage接口,其中RequestMessage的具体实现有四种,分别是:

  • ChunkFetchRequest:请求获取流的单个块的序列。ChunkFetch消息用于抽象所有spark中涉及到数据拉取操作时需要传输的消息
  • RpcRequest:此消息类型由远程的RPC服务端进行处理,是一种需要服务端向客户端回复的RPC请求信息类型。
  • OneWayMessage:此消息也需要由远程的RPC服务端进行处理,与RpcRequest不同的是不需要服务端向客户端回复。
  • StreamRequest:此消息表示向远程的服务发起请求,以获取流式数据。Stream消息主要用于driver到executor传输jar、file文件等。

由于OneWayMessage 不需要响应,所以ResponseMessage的对于成功或失败状态的实现各有三种,分别是:

  • ChunkFetchSuccess:处理ChunkFetchRequest成功后返回的消息;
  • ChunkFetchFailure:处理ChunkFetchRequest失败后返回的消息;
  • RpcResponse:处理RpcRequest成功后返回的消息;
  • RpcFailure:处理RpcRequest失败后返回的消息;
  • StreamResponse:处理StreamRequest成功后返回的消息;
  • StreamFailure:处理StreamRequest失败后返回的消息;

服务端启动时序图

服务端启动

服务端响应时序图

服务端响应 第一阶段,IO接收。TransportRequestHandler是netty的回调handler,它会根据wire format(下文会介绍)解析好一个完整的数据包,交给NettyRpcEnv做反序列化,如果是RPC调用会构造RpcMessage,然后回调RpcHandler的方法处理RpcMessage,内部会调用Dispatcher做RpcMessage的投递,放到Inbox中,到此结束。

第二阶段,IO响应。MessageLoop获取带处理的RpcMessage,交给Dispatcher中的ThreadPool做处理,实际就是调用RpcEndpoint的业务逻辑,通过RpcCallContext将消息序列化,通过回调函数,告诉TransportRequestHandler这有一个消息处理完毕,响应回去。

这里请重点体会异步处理带来的便利,使用Reactor和Actor mailbox的结合的模式,解耦了消息的获取以及处理逻辑。

客户端响应时序图

客户端响应 客户端一般需要先建立RpcEnv,然后获取RpcEndpointRef。

第一阶段,IO发送。利用RpcEndpointRef做send或者ask动作,这里以send为例,send会先进行消息的序列化,然后投递到指定地址的Outbox中,Outbox如果发现连接未建立则先尝试建立连接,然后调用底层的TransportClient发送数据,直接通过该netty的API完成,完成后即可返回,这里返回了UUID作为消息的标识,用于下一个阶段的回调,使用的角度来说可以返回一个Future,客户端可以阻塞或者继续做其他操作。

第二,IO接收。TransportResponseHandler接收到远程的响应后,会先做反序列号,然后回调第一阶段的Future,完成调用,这个过程全部在Reactor线程中完成的,通过Future做线程间的通知。

RPC通信架构图

Dispatcher、Inbox分发处理请求

image.png

OutBox处理请求

image.png