Spark的TorrentBroadcast:概念和原理

devos發表於2015-08-16

依據Spark 1.4.1原始碼

SparkContext的broadcast方法

註釋

可以用SparkContext將一個變數廣播到所有的executor上,使得所有executor都能獲取這個變數代表的資料。

SparkContext對於broadcast方法的註釋為:

/**
* Broadcast a read-only variable to the cluster, returning a
* [[org.apache.spark.broadcast.Broadcast]] object for reading it in distributed functions.
* The variable will be sent to each cluster only once.
*/
def broadcast[T: ClassTag](value: T): Broadcast[T]

"Broadcast a read-only variable to the cluster",指出了這個變數是隻讀的。只所以是隻讀的,我認為是因為每個executor的多個task之間共享一個被廣播的變數,所以存線上程安全的問題,但是如果多個執行緒都“讀”一個變數,仍然不能保證讀操作是執行緒安全的,這裡或許仍然需要Spark再說明一下。

(以下需要仔細區分“Broadcast變數”和"被broadcast的變數“)

"returning a Broadcast object for reading it in distributed functions",這句指出了Broadcast變數是在被分佈執行的函式中使用。而被分散式執行的函式是被包含在Spark的task中分發到各個executor執行的,因此Broadcast變數作為被分發的task的一部分,需要隨task一起經過序列化和反序列化的過程。

但是被broadcast的變數可能很大,而分發task的機制不是為了在叢集中分發大量資料實現的,所以被broadcast的變數不宜隨task一起簡單地序列化和反序列化。TorrentBroadcast通過一些巧妙的方法,避免了被廣播的資料隨分散式執行的函式一起序列化。

總之,Broadcast變數是隨task進行序列化反序列化的,而被broadcast的變數則通過另外的手段到達executor。

Broadcast變數實際是被廣播變數的容器,使用時需要使用其value方法從中取出被廣播的變數,而value方法是broadcast機制實現的關鍵之一。

呼叫關係

def broadcast[T: ClassTag](value: T): Broadcast[T] = {
    assertNotStopped()
    if (classOf[RDD[_]].isAssignableFrom(classTag[T].runtimeClass)) {
      // This is a warning instead of an exception in order to avoid breaking user programs that
      // might have created RDD broadcast variables but not used them:
      logWarning("Can not directly broadcast RDDs; instead, call collect() and "
        + "broadcast the result (see SPARK-5063)")
    }
    val bc = env.broadcastManager.newBroadcast[T](value, isLocal)
    val callSite = getCallSite
    logInfo("Created broadcast " + bc.id + " from " + callSite.shortForm)
    cleaner.foreach(_.registerBroadcastForCleanup(bc))
    bc
  }

首先判斷一個被廣播的是不是一個RDD,因為RDD是distributed,一個RDD變數並不包含有RDD中的資料集,也無法在每個executor直接獲取整個RDD的資料(而是應該在driver端collect RDD的資料,然後再廣播),所以Spark不支援廣播RDD(但實際上可以做得到在廣播RDD時,在每個executor上得到RDD中的所有資料,只是Spark沒有去實現)。注意,即使廣播了RDD也不會拋異常。

然後使用BroadcastManager的newBroadcast方法來生成一個Broadcast變數。而BroadcastManager會去呼叫BroadcastFactory的newBroadcast方法獲取Broadcast變數。

Spark裡的BroadcastFactor是可以配置的

 val broadcastFactoryClass =
          conf.get("spark.broadcast.factory", "org.apache.spark.broadcast.TorrentBroadcastFactory")

 broadcastFactory =
          Class.forName(broadcastFactoryClass).newInstance.asInstanceOf[BroadcastFactory]

預設值即是TorrentBroadcastFactory, 它的newBroadcast方法只是new一個TorrentBroadcast物件。

 override def newBroadcast[T: ClassTag](value_ : T, isLocal: Boolean, id: Long): Broadcast[T] = {
    new TorrentBroadcast[T](value_, id)
  }

所以TorrentBroadcast機制的核心就在TorrentBroadcast類。

TorrentBroadcast的原理

註釋


/**
* A BitTorrent-like implementation of [[org.apache.spark.broadcast.Broadcast]].
*
* The mechanism is as follows:
*
* The driver divides the serialized object into small chunks and
* stores those chunks in the BlockManager of the driver.
*
* On each executor, the executor first attempts to fetch the object from its BlockManager. If
* it does not exist, it then uses remote fetches to fetch the small chunks from the driver and/or
* other executors if available. Once it gets the chunks, it puts the chunks in its own
* BlockManager, ready for other executors to fetch from.
*
* This prevents the driver from being the bottleneck in sending out multiple copies of the
* broadcast data (one per executor) as done by the [[org.apache.spark.broadcast.HttpBroadcast]].
*
* When initialized, TorrentBroadcast objects read SparkEnv.get.conf.
*
* @param obj object to broadcast
* @param id A unique identifier for the broadcast variable.
*/

這段註釋說明了TorrentBroadcast實現的原理,其中關鍵的部分在於利用BlockManager的分散式結構來儲存和獲取資料塊。

driver把序列化後的物件(即value)分塊很多塊,並且把這些塊存到driver的BlockManager裡。

在executor端,executor首先試圖從自己的BlockManager中獲取被broadcast變數的塊,如果它不存在,就使用遠端抓取從driver 以及/或者其它的

executor上獲取這個塊。當executor獲取了一個塊,它就把這個塊放在自己的BlockManager裡,以使得其它的executor可以抓取它。

這防止了被廣播的資料只從driver端被拷貝,這樣當要拷貝的次數很多的時候(每個executor都會拷貝一次),driver端容易成為瓶頸(就像HttpBroadcast所做的一樣).

這段註釋時的代詞用得不準確,executor是沒有專門的機制用於處理Broadcast變數的,所有的魔法都在Broadcast變數本身。可以這麼描述:

driver端把資料分塊,每個塊做為一個block存進driver端的BlockManager,每個executor會試圖獲取所有的塊,來組裝成一個被broadcast的變數。“獲取塊”的方法是首先從executor自身的BlockManager中獲取,如果自己的BlockManager中沒有這個塊,就從別的BlockManager中獲取。這樣最初的時候,driver是獲取這些塊的唯一的源,但是隨著各個BlockManager從driver端獲取了不同的塊(TorrentBroadcast會有意避免各個executor以同樣的順序獲取這些塊),“塊”的源就多了起來,每個executor就可能從多個源中的一個,包括driver和其它executor的BlockManager中獲取塊,這要就使得流量在整個叢集中更均勻,而不是由driver作為唯一的源。

原理就是這樣啦,但是TorrentBoradcast的實現有很多有意思的細節,可以仔細分析一下。

 

相關文章