Spark内置RPC框架的基本架构

本文深入探讨了Spark的RPC框架发展历程,从早期版本依赖Akka到2.0版本引入自定义RPC框架的过程。详细介绍了该框架的基本架构及其核心组件,包括TransportContext、TransportConf、RpcHandler等,帮助读者理解其在网络通信中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文是读耿嘉安先生的《Spark内核设计的艺术:架构设计与实现》的读书笔记;
书中代码实现讲得少一些,更多的是对Spark内核架构的分析与解读
书讲得比较深看实在看不去,就一个字一个字地打出来,甚至图 都再画一遍。
打完之后再回过头来温习两遍,将书中比较有价值的、重要的东西梳理一下形成此文

Spark的RPC框架历史介绍

在Spark中很多地方都涉及网络通信,比如Spark各个组件间的消息互通、用户文件与Jar包的上传,节点间的Shuffle过程,Block数据的复制与备份等。
在Spark0.x.x与Spark1.x.x版本中,组件间的消息通信主要借助于Akka,使用Akka可以轻松地构建强有力的高并发与分布式应用。
但是Akka在Spark2.0.0版本中被移除了,Spark官网文档对此的描述为:"Akka的依赖被移除了,因此用户可以使用任何版本的Akka来编程了。”
Spark团队的决策者或许认为对Akka具体版本的依赖,限制了用户对Akka不同版本的使用。
在Spark 1.x.x版本中,用户文件与Jar包的上传采用了由Jetty实现的HttpFileServer,但在Spark2.0.0版本中它也被废弃了,现在使用的是基于Spark内置RPC框架的NettyStreamManager。节点间的Shuffle过程和Block数据的复制与备份这两个部分在Spark2.0.0版本中依然沿用了Netty,通过对接口和程序进行重新设计,将各个组件间的消息互通、用户文件与Jar包的上传等内容统一纳入Spark的RPC框架体系中。

Spark的RPC框架基本架构

RPC框架的基本架构:
Spark内置PRC框架的基本架构_01.png

TransportContext内部包含传输上下文的配置信息TransportConf和 对客户端请求消息进行处理的RpcHandlerTransportConf在创建TransportClientFactoryTransportServer时都是必须的,而RpcHandler只用于创建TransportServerTransportClientFactory是RPC客户端的工厂类。TransportServer是RPC服务端的实现。
图 中记号1的含义:
通过调用TransportContext的createClientFactory方法创建传输客户端工厂TransportClientFactory的实例。在构造TransportClientFactory的实例时,还会传递客户端引导程序TransportClientBootstrap的列表。此外,TransportClientFactory内部还存在针对每个Socket地址的连接池ClientPool。这个连接池缓存的定义如下:

private final ConcurrentHashMap<SocketAddress, ClientPool> connectionPool; 

ClientPool的类型定义如下:

private static class ClientPool {
    TransportClient[] clients;
    Object[] locks;

    ClientPool(int size){
        clients = new TransportClient[size];
        locks = new Object[size];
        for(int i = 0; i < size; i++){
            locks[i] = new Object();
        }
    }
}

由此可见,ClientPool实际是由TransportClient的数组构成的,而Locks数组中的Obejct与Clients数组中的TransportClient按照数组索引一一对应,通过对每个TransportClient分别采用不同的锁,降低并发情况下线程间对锁的争用,进而减少阻塞,提高并发度。

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

上面介绍了Spark内置RPC框架的基本架构,下面正式介绍一下Spark的RPC框架所包含的各个组件:

Spark的RPC框架包含的各个组件

  • TransportContext
    传输上下文,包含了用于创建传输服务端(TransportServer)和传输客户端工厂(TransportClientFactory)的上下文信息,并支持使用TransportChannelHandler设置Netty提供的SocketChannel的Pipeline的实现。

  • TransportConf
    传输上下文的配置信息

  • RpcHandler
    对调用传输客户端(TransportClient)的sendRPC方法发送的消息进行处理的程序。

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

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

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

  • RpcResponseCallback
    RpcHandler对请求的消息处理完毕后进行回调的接口。

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

  • ClientPool
    在两个对等节点间维护的关于TransportClient的池子。ClientPoolTransportClientFactory的内部组件。

  • TransportClient
    RPC框架的客户端,用于获取预先协商好的流中连续块。TransportClient旨在允许有效传输大量数据,这些数据将被拆分成几百KB到几MB的块。当TransportClient处理从流中获取的块时,实际的设置是在传输层之外完成的。sendRPC方法能够在客户端和服务端的同一水平线的通信进行这些设置。

  • TransportClientBootstrap
    当服务端响应客户端连接时在客户端进行一次的引导程序。

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

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

  • TransportChannelHandler
    代理由TransportRequestHandler处理的请求和 由TransportResponseHandler处理的响应,并加入传输层的处理。

  • TransportServerBootstrap
    当客户端连接到服务端执行一次的引导程序。

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

为什么需要MessageEncoderMessageDecoder
因为在基于流的传输里(比如TCP/IP),接收到的数据首先会被存储到一个Socket接收缓冲里。不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列,即使你发送了2个独立的数据包,操作系统也不会作为 2个消息处理,而仅仅认为是一连串的字节,因此不能保证远程写入的数据会被准确地读取,举个例子,我们假设操作系统的TCP/IP协议栈已经接收了3个数据包:ABC、DEF、GHI。由于基于流传输的的这种统一的性质,你的应用程序在读取数据的时候有很大的可能性被分成下面的片段:AB、CDEFG、H、I。因此,接收方不管是客户端还是服务端都应该把接收到的数据整理成一个或者多个更有意义并且让程序的逻辑更好理解的数据。

<think>好的,我需要帮助用户了解在Netty+Kyro+Zookeeper的RPC框架项目中是否使用了Snappy压缩算法,以及可能的替代方案。首先,我应该回顾每个组件的作用和相关技术。 Netty是一个高性能的网络通信框架,常用于构建RPC框架的底层传输层。它支持多种编解码器和协议,可能集成压缩功能。Kyro是一个高效的Java序列化库,序列化后的数据体积较小,但可能本身不包含压缩功能,需要结合其他压缩算法。Zookeeper主要用于服务发现和协调,通常不直接处理数据传输的压缩。 接下来,Snappy压缩算法是由Google开发的,以高速度和合理的压缩率著称,适合需要低延迟的场景。在RPC框架中,数据压缩可以减小网络传输量,提升性能。需要确认的是,Kyro是否支持Snappy或者是否需要额外配置。例如,用户可能在序列化后手动应用Snappy压缩,或者通过Netty的Handler集成Snappy。 另外,用户提到的替代方案可能包括Gzip、LZ4等。Gzip压缩率更高但速度较慢,适合带宽敏感的场景;LZ4速度比Snappy更快,但压缩率可能稍低。需要比较这些算法的优缺点,帮助用户选择合适的方案。 还需要参考引用资料中的相关内容。比如引用[3]提到使用fastutil和原生类型减少内存消耗,以及序列化RDD存储。这可能与数据序列化和压缩有关,但需要确认是否直接相关。如果项目中使用了Spark,可能涉及RDD的压缩设置,但用户的问题主要围绕Netty+Kyro+Zookeeper的RPC框架。 可能的步骤是:首先检查Kyro是否内置支持Snappy,或者需要扩展;其次查看Netty的ChannelHandler是否有Snappy的实现;然后考虑在序列化前后添加压缩步骤;最后对比其他压缩算法作为替代方案。 需要确保回答结构清晰,分点说明每个组件的角色、Snappy的应用方式以及替代方案,并引用相关资料支持建议。</think>在Netty+Kyro+Zookeeper的RPC框架项目中,压缩算法的选择直接影响网络传输效率和系统性能。以下是具体分析: ### 1. **Netty与压缩算法** Netty本身不强制使用特定压缩算法,但提供了灵活的编解码接口。开发者可通过以下方式集成Snappy: - **添加Snappy依赖**:例如在Maven中引入`netty-all`库(包含Snappy支持)。 - **配置ChannelPipeline**:在Netty的Handler链中添加`SnappyFrameEncoder`和`SnappyFrameDecoder`,实现数据流的压缩/解压缩[^1]。 - **性能权衡**:Snappy的压缩速度优于Gzip,但压缩率较低,适合低延迟场景。 ### 2. **Kyro序列化与压缩** Kyro是高效的二进制序列化库,但**默认不包含压缩功能**。需在序列化后手动调用压缩算法: ```java // 示例:Kyro序列化后应用Snappy压缩 Output output = new Output(new ByteArrayOutputStream()); kryo.writeObject(output, data); byte[] serialized = output.toBytes(); byte[] compressed = Snappy.compress(serialized); ``` Kyro的高效性可能减少对压缩的需求,但大数据场景仍需额外压缩。 ### 3. **Zookeeper的角色** Zookeeper主要用于服务注册与发现,**不直接参与数据传输压缩**。其存储的元数据(如服务节点信息)通常较小,一般无需压缩。 ### 4. **Snappy的替代方案** 若需替换Snappy,可考虑以下算法: - **LZ4**:速度比Snappy更快,压缩率相近,适合极致性能需求。 - **Gzip/Zstandard**:Gzip压缩率高但速度慢;Zstandard(Zstd)平衡速度与压缩率,适合带宽敏感场景。 - **集成方式**:在Netty中使用`CompressionOptions`配置,或通过Kyro的扩展接口实现。 ### 5. **性能优化建议** - **测试对比**:通过实际业务数据测试不同算法,平衡CPU占用与带宽节省。 - **动态选择**:可设计协议头字段,允许不同场景动态切换压缩算法。 - **参考Spark优化经验**:如引用[3]提到的减少Java对象封装、使用原生类型数组,可降低序列化前的内存占用,间接提升压缩效率[^3]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值