溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spark的閉包清理機制怎么理解

發(fā)布時間:2021-12-16 21:15:25 來源:億速云 閱讀:183 作者:柒染 欄目:大數(shù)據(jù)

Spark的閉包清理機制怎么理解,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

關(guān)于Spark任務(wù)運行時發(fā)生不可序列話的問題。今天就統(tǒng)一講解一下這塊的內(nèi)容。

首先,要先讀懂scala的閉包是怎么回事兒。

簡單理解scala的閉包

接著就是要理解Spark 算子閉包生成及我們編寫的閉包執(zhí)行的原理。接下來我們就拿map和mapPartition兩個算子來開啟本文講解:

def map[U: ClassTag](f: T => U): RDD[U] = withScope {
   val cleanF = sc.clean(f)
   new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
 }

def mapPartitions[U: ClassTag](
     f: Iterator[T] => Iterator[U],
     preservesPartitioning: Boolean = false): RDD[U] = withScope {
   val cleanedF = sc.clean(f)
   new MapPartitionsRDD(
     this,
     (context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(iter),
     preservesPartitioning)
 }

對于任務(wù)劃分,調(diào)度,執(zhí)行,結(jié)果返回的原理浪尖就不在這里擴展了,浪尖在知識星球里分享過一套Spark 源碼的視頻,可以參考閱讀。

map和mapPartitions的區(qū)別面試常考的,對于兩者的區(qū)別從源碼里看很明顯,一個是f被迭代器迭代調(diào)用,一個是f的參數(shù)是迭代器。浪尖很早以前發(fā)過一篇文章,徹底講解過foreach和foreachPartition的區(qū)別。可以參考理解

Spark源碼系列之foreach和foreachPartition的區(qū)別

回到正題,之所以會發(fā)生不可序列化的錯誤,主要原因是傳遞給map的f函數(shù)不是在driver端執(zhí)行的,所以會被序列化傳輸?shù)絜xecutor節(jié)點,然后在executor節(jié)點反序列化然后執(zhí)行。假如f函數(shù)里引用了map外部不可序列化的對象就會報不可序列化的異常。

但是,很多時候我們并沒有直接去在閉包里使用不可序列化的對象,這個時候報異常就有點不合適了。比如下面的例子:

*   class SomethingNotSerializable {
  *     def someValue = 1
  *     def scope(name: String)(body: => Unit) = body
  *     def someMethod(): Unit = scope("one") {
  *       def x = someValue
  *       def y = 2
  *       scope("two") { println(y + 1) }
  *     }
  *   }

此示例中,scope(two) 不可序列化,因為它引用了scope(one)(通過y),而scope(one)引用了SomethingNotSerializable(通過someValue)。但是,其實scope(two)并不直接依賴于SomethingNotSerializable。假如這種情況下拋出不可序列化異常就不科學(xué)了,所以Spark會對閉包進行一些清理操作,也即是本文中所要講的。

主要工具類是ClosureCleaner。該工具的主要作用是遍歷閉包的層次結(jié)構(gòu),并且將沒有被閉包實際引用的鏈路設(shè)置為null,但是仍然包含在已經(jīng)編譯的匿名類中。請注意直接修改封閉中的閉包是不安全的,因為可能有其他代碼路徑會依賴于他們。所以,我們會克隆封閉中的閉包并且相應(yīng)地設(shè)置父指針。

默認(rèn)情況下,可以傳遞清除閉包。這就意味著,我們需要檢測封閉對象是否由起始對象實際引用,(要么直接引用要么間接引用),如果沒有被實際使用則從層次結(jié)構(gòu)中切斷這些閉包。換句話說,除了清空無用字段的引用之外,也會將沒有被起始閉包引用的引用封閉對象的父指針清空。傳遞性的確定是通過遍歷閉包所調(diào)用的

再回到前面的例子,scope(two) 不可序列化,因為它引用了scope(one)(通過y),而scope(one)引用了SomethingNotSerializable(通過someValue)。但是,其實scope(two)并不直接依賴于SomethingNotSerializable。這就意味著我們可以安全的將其副本scope(one)的父指針清空,同時將其設(shè)置為scope(two)的父級,這樣scope(two)就不再需要間接傳遞引用SomethingNotSerializable了。

解決方法

實現(xiàn)序列化是最直接的,假如不能的話。那就讀下面的話:

那么為了不實現(xiàn)序列化還能盡量避免不可序列化錯誤,就不要在map等算子里引用外部變量,而是直接在算子中實例化,假如每次實例化代價高,那就使用mapPartitions。

看完上述內(nèi)容,你們掌握Spark的閉包清理機制怎么理解的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI