依據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的實現有很多有意思的細節,可以仔細分析一下。