读《Spark内核设计的艺术 架构设计与实现》笔记之三----SparkConf & 内置的RPC框架

SparkConf

SparkConf 是Spark的配置类,Spark中的每一个组件都直接或者间接的使用这个类存储的属性.SparkConf中,使用ConcurrentHaskMap来存储这些属性,其中key以及value都是String类型的.

/** 线程安全的,用于存储配置的各种属性 */
  private val settings = new ConcurrentHashMap[String, String]()

SparkConf的构造器中有一个布尔类型的loadDefaults,当loadDefaults为true时,将会从系统属性中加载Spark配置,而这些配置的key都是以spark.开头的属性:

if (loadDefaults) {
  // 加载系统中以spark.开头的系统属性
  loadFromSystemProperties(false)
}

/**
  * 加载系统中以spark.开始的系统属性
  *
  * @param silent 是否检查过时属性并打印警告️信息 true:不检查  false 检查
  * @return
  */
private[spark] def loadFromSystemProperties(silent: Boolean): SparkConf = {
  // Load any spark.* system properties
  for ((key, value) <- Utils.getSystemProperties if key.startsWith("spark.")) {
    set(key, value, silent)
  }
  this
}

我们可以从loadFromSystemProperties方法中可以看到,使用Utils工具类获取到系统属性后,进行遍历,遍历时如果是以spark.开头的,就调用SparkConf的set方法存储到setting属性中.set方法的源码如下:

private[spark] def set(key: String, value: String, silent: Boolean): SparkConf = {
  // 检查key和value,保证key和value都不为null
  if (key == null) {
    throw new NullPointerException("null key")
  }
  if (value == null) {
    throw new NullPointerException("null value for " + key)
  }
  // 是否检查过时警告:false 检查
  if (!silent) {
    logDeprecationWarning(key)
  }
  // 如果key和value都不为null,将key和value存储到settings中
  settings.put(key, value)
  // 返回当前SparkConf的实例
  this
}

从SparkConf的源码中可以看到,set方法被重载了多个,但是,最终都下面这一个set方法:

/** Set a configuration variable. */
def set(key: String, value: String): SparkConf = {
  set(key, value, false)
}

常用的通过SparkConf设置Master,AppName等属性:

/**
  * The master URL to connect to, such as "local" to run locally with one thread, "local[4]" to
  * run locally with 4 cores, or "spark://master:7077" to run on a Spark standalone cluster.
  */
def setMaster(master: String): SparkConf = {
  set("spark.master", master)
}

/** Set a name for your application. Shown in the Spark web UI. */
def setAppName(name: String): SparkConf = {
  set("spark.app.name", name)
}

/** Set JAR files to distribute to the cluster. */
def setJars(jars: Seq[String]): SparkConf = {
  for (jar <- jars if (jar == null)) logWarning("null jar passed to SparkContext constructor")
  set("spark.jars", jars.filter(_ != null).mkString(","))
}

Spark提供了设置属性,当然也提供了诸多获取属性的方法,但是最终调用的也都是下面这一个:

/** Get a parameter as an Option */
def getOption(key: String): Option[String] = {
  Option(settings.get(key)).orElse(getDeprecatedConfig(key, this))
}

在SparkConf的伴生对象中,还将对应版本号过时的配置信息存储到deprecatedConfigs中,对饮版本可选参数存储到configsWithAlternatives中,更多关于配置信息,请参见源码.

在有些情况下,同一个SparkConf实例中的配置信息需要被Spark中的多个组件共用,在SparkConf的源码中可以看到,其继承了Cloneable特质并实现了clone方法,功能与java的一样,就是可以通过克隆来创建.

Spark内置的RPC框架

在Spark中很多地方都涉及到网络通信,比如Spark各个组件间的消息互通,用户文件与Jar包上传,节点间的Shuffle过程,Block数据的复制与备份等.

在Spark2.0,0版本中节点间Shuffle过程和Block数据的复制与备份依然使用Netty.通过对接口和程序接口的重新设计,将各个组件间的消息互通,用户文件与Jar包上传等内容统一纳入Spark的RPC框架体系中.

Spark内置RPC框架的基本架构:

Spark内置RPC框架的基本架构

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

①表示通过调用TransportContext的createClientFactory方法创建传输客户端工厂TransportClientFactory的实例.在构造TransportClientFactory的时候,还会传递客户端引导程序TransportClientBootstrap的列表,此外,TransportClientFactory内部还存在针对每一个Socket地址的连接池ClientPool,这个连接池的定义如下:

private final ConcurrentHashMap<SocketAddress, ClientPool> connectionPool;

ClientPool定义如下:

private static class ClientPool {
        // ClientPool由TransportClient构成
        TransportClient[] clients;
        // 与每一个TransportClient 一一对应的锁对象,
        // 通过对每个TransportClient分别采用不同的锁,降低并发情况下线程间对锁的争用,减少阻塞,提高并发度
        Object[] locks;

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

②标示通过调用TransportContext的createServer方法创建传输服务端TransportServer的实例.在狗仔TransportServer的实例时,需要传递TransportContext,host,port,RpcHandler以及服务端引导程序TransportServerBootstrap的列表.

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

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

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

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

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

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

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

  • RpcResponseCallBack:RpcHandler对请求的消息处理完毕后进行回掉的接口.

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

  • ClientPool :在两个对等节点间维护的关于TransportClient的池子.ClientPool时TransportClientFacctory的内部组件.

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

  • TransportClintBootstrap:当服务端响应客户端连接时在客户端执行一次的引导程序.

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

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

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

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

TransportConf

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

可以通过SparkTransportConf的fromSparkConf方法获取TransportConf实例,获取时需要SparkConf实例,module名称,用于处理网络传输的内核数numUsableCores
SparkTransportConf源码:

  object SparkTransportConf {
  /**
    * 使用Netty线程数的上限
    * 实际上,只需要2-4核就可以传输速度就可以达到10Gb/s,每个内核的使用初始开销大约为32M堆外内存.
    */
  private val MAX_DEFAULT_NETTY_THREADS = 8

  /**
    * Utility for creating a [[TransportConf]] from a [[SparkConf]].
    *
    * @param _conf          the [[SparkConf]]
    * @param module         the module name
    * @param numUsableCores 如果非0,则将服务器和客户端线城数限制为使用给定的核数,而不是机器的所有核数.只有为0时才使用及其的所有核数.
    *
    *
    */
  def fromSparkConf(_conf: SparkConf, module: String, numUsableCores: Int = 0): TransportConf = {
    val conf = _conf.clone

    // Specify thread configuration based on our JVM's allocation of cores (rather than necessarily
    // assuming we have all the machine's cores).
    // NB: Only set if serverThreads/clientThreads not already set.
    val numThreads = defaultNumThreads(numUsableCores)
    conf.setIfMissing(s"spark.$module.io.serverThreads", numThreads.toString)
    conf.setIfMissing(s"spark.$module.io.clientThreads", numThreads.toString)

    new TransportConf(module, new ConfigProvider {
      override def get(name: String): String = conf.get(name)

      override def get(name: String, defaultValue: String): String = conf.get(name, defaultValue)

      override def getAll(): java.lang.Iterable[java.util.Map.Entry[String, String]] = {
        conf.getAll.toMap.asJava.entrySet()
      }
    })
  }

  // 默认的线程数
  private def defaultNumThreads(numUsableCores: Int): Int = {
    // 如果numUsableCores<=0 则使用系统可用的处理器数量,否则使用用户设置的数量
    // 但是,不可能所有的核数都用于网络传输,所以这里有一个前提条件,就是不管是使用系统可用的核数还是用户设定的核数,都必须≤用于网络传输的核数上线(MAX_DEFAULT_NETTY_THREADS)
    val availableCores =
    if (numUsableCores > 0) numUsableCores else Runtime.getRuntime.availableProcessors()
    math.min(availableCores, MAX_DEFAULT_NETTY_THREADS)
  }
}

从fromSparkConf方法的源码中可以看到,直接通过new的方式创建了一个TransportConf对象,在TransportConf的构造方法中,传递了一个module,和一个ConfigProvider的匿名子类,在ConfigProvider的匿名子类中,实现了ConfigProvider中申明的抽象方法,可以看到,TransportConf中的配置信息也都是通过SparkConf来获取的.

在TransportConf类中,有两个成员变量以及配置项,其中,两个成员变量分别是String类型的module,和ConfigProvider,而ConfigProvider是一个抽象类,申明了两个抽象方法get和getAll以及getInt,getLong,getBoolean,getDouble几个已经实现的方法,这几个已经实现的方法都主要是通过申明的get方法来获取所需要的配置项.

TransportConf中列出的配置项以及释义(个人理解):

/**
     * IO mode:O mode: nio or epoll,使用者可通过ioMode()方法获取,默认使用NIO
     */
    private final String SPARK_NETWORK_IO_MODE_KEY;
    /**
     * 在netty层,是否使用堆外内存缓存.可通过preferDirectBufs()方法获取,如果返回是true,则使用堆外内存缓存,默认true
     */
    private final String SPARK_NETWORK_IO_PREFERDIRECTBUFS_KEY;
    /**
     * 网络连接超时,可通过connectionTimeoutMs()方法获取.单位是毫秒,默认120*1000毫秒
     */
    private final String SPARK_NETWORK_IO_CONNECTIONTIMEOUT_KEY;
    /**
     * 请求传入队列的对象长度,默认-1,也就是没有最大限制可通过backLog方法获取
     */
    private final String SPARK_NETWORK_IO_BACKLOG_KEY;
    /**
     * 用于获取数据的两个节点之间的并发连接数。可用numConnectionsPerPeer方法获取.
     */
    private final String SPARK_NETWORK_IO_NUMCONNECTIONSPERPEER_KEY;
    /**
     * 服务端线程池中使用的线程数,为核数的两倍,可通过serverThreads方法获取
     */
    private final String SPARK_NETWORK_IO_SERVERTHREADS_KEY;

    /**
     * 客户端线程池中使用的线程数,为核数的两倍,可通过clientThreads方法获取
     */
    private final String SPARK_NETWORK_IO_CLIENTTHREADS_KEY;
    /**
     * 接收缓冲区大小 可使用receiveBuf获取
     * 注意:接收缓冲区和发送缓冲区的最佳大小应为
     * 延迟*网络带宽。
     * 假设延迟=1毫秒,网络带宽=10 Gbps
     * 缓冲区大小应为~1.25MB
     */
    private final String SPARK_NETWORK_IO_RECEIVEBUFFER_KEY;
    /**
     * 发送缓冲大小,可使用sendBuf获取
     */
    private final String SPARK_NETWORK_IO_SENDBUFFER_KEY;
    /**
     * 身份验证消息交换的单次往返超时,单位毫秒,默认30*1000毫秒,可通过authRTTimeoutMs方法获取
     */
    private final String SPARK_NETWORK_SASL_TIMEOUT_KEY;
    /**
     * 每次请求最大尝试次数,如连接超时,如果为0,将不做任何尝试,可通过maxIORetries方法获取
     */
    private final String SPARK_NETWORK_IO_MAXRETRIES_KEY;
    /**
     * 发生IO异常后,下次尝试的等待时间,默认单位毫秒,默认5*1000毫秒,可通过ioRetryWaitTimeMs方法获取
     */
    private final String SPARK_NETWORK_IO_RETRYWAIT_KEY;
    /**
     * 是否延迟初始化文件描述符。如果为真,则文件描述符为
     * 仅在要传输数据时创建。这可以减少打开文件的数量。
     * 可通过lazyFileDescriptor方法获取
     */
    private final String SPARK_NETWORK_IO_LAZYFD_KEY;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值