您好,登錄后才能下訂單哦!
在 Spark 中,提供了兩種類型的共享變量:累加器 (accumulator) 與廣播變量 (broadcast variable):
這里先看一個具體的場景,對于正常的累計求和,如果在集群模式中使用下面的代碼進行計算,會發(fā)現(xiàn)執(zhí)行結(jié)果并非預(yù)期:
var counter = 0
val data = Array(1, 2, 3, 4, 5)
sc.parallelize(data).foreach(x => counter += x)
println(counter)
counter 最后的結(jié)果是 0,導(dǎo)致這個問題的主要原因是閉包。
1. Scala 中閉包的概念
這里先介紹一下 Scala 中關(guān)于閉包的概念:
var more = 10
val addMore = (x: Int) => x + more
如上函數(shù) addMore
中有兩個變量 x 和 more:
按照定義:在創(chuàng)建函數(shù)時,如果需要捕獲自由變量,那么包含指向被捕獲變量的引用的函數(shù)就被稱為閉包函數(shù)。
2. Spark 中的閉包
在實際計算時,Spark 會將對 RDD 操作分解為 Task,Task 運行在 Worker Node 上。在執(zhí)行之前,Spark 會對任務(wù)進行閉包,如果閉包內(nèi)涉及到自由變量,則程序會進行拷貝,并將副本變量放在閉包中,之后閉包被序列化并發(fā)送給每個執(zhí)行者。因此,當在 foreach 函數(shù)中引用 counter
時,它將不再是 Driver 節(jié)點上的 counter
,而是閉包中的副本 counter
,默認情況下,副本 counter
更新后的值不會回傳到 Driver,所以 counter
的最終值仍然為零。
需要注意的是:在 Local 模式下,有可能執(zhí)行 foreach
的 Worker Node 與 Diver 處在相同的 JVM,并引用相同的原始 counter
,這時候更新可能是正確的,但是在集群模式下一定不正確。所以在遇到此類問題時應(yīng)優(yōu)先使用累加器。
累加器的原理實際上很簡單:就是將每個副本變量的最終值傳回 Driver,由 Driver 聚合后得到最終值,并更新原始變量。
SparkContext
中定義了所有創(chuàng)建累加器的方法,需要注意的是:被中橫線劃掉的累加器方法在 Spark 2.0.0 之后被標識為廢棄。
使用示例和執(zhí)行結(jié)果分別如下:
val data = Array(1, 2, 3, 4, 5)
// 定義累加器
val accum = sc.longAccumulator("My Accumulator")
sc.parallelize(data).foreach(x => accum.add(x))
// 獲取累加器的值
accum.value
在上面介紹中閉包的過程中我們說道每個 Task 任務(wù)的閉包都會持有自由變量的副本,如果變量很大且 Task 任務(wù)很多的情況下,這必然會對網(wǎng)絡(luò) IO 造成壓力,為了解決這個情況,Spark 提供了廣播變量。
廣播變量的做法很簡單:就是不把副本變量分發(fā)到每個 Task 中,而是將其分發(fā)到每個 Executor,Executor 中的所有 Task 共享一個副本變量。
// 把一個數(shù)組定義為一個廣播變量
val broadcastVar = sc.broadcast(Array(1, 2, 3, 4, 5))
// 之后用到該數(shù)組時應(yīng)優(yōu)先使用廣播變量,而不是原值
sc.parallelize(broadcastVar.value).map(_ * 10).collect()
RDD Programming Guide
更多大數(shù)據(jù)系列文章可以參見 GitHub 開源項目: 大數(shù)據(jù)入門指南
免責聲明:本站發(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)容。