讀《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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章