4.4 共享變量
一般來(lái)說(shuō),當(dāng)一個(gè)被傳遞給Spark操作(例如,Map和Reduce)的函數(shù)在一個(gè)遠(yuǎn)程集群上運(yùn)行時(shí),該函數(shù)實(shí)際上操作的是它用到的所有變量的獨(dú)立副本。
這些變量會(huì)被復(fù)制到每一臺(tái)機(jī)器,在遠(yuǎn)程機(jī)器上對(duì)變量的所有更新都不會(huì)傳回主驅(qū)動(dòng)程序。默認(rèn)來(lái)說(shuō),當(dāng)Spark以多個(gè)Task在不同的Worker上并發(fā)運(yùn)行一個(gè)函數(shù)時(shí),它傳遞每一個(gè)變量的副本并緩存在Worker上,用于每一個(gè)獨(dú)立Task運(yùn)行的函數(shù)中。
有時(shí),我們需要變量能夠在任務(wù)中共享,或者在任務(wù)與驅(qū)動(dòng)程序之間共享。
而Spark提供兩種模式的共享變量:廣播變量和累加器。Spark的第二個(gè)抽象便是可以在并行計(jì)算中使用的共享變量。
□廣播變量:可以在內(nèi)存的所有節(jié)點(diǎn)中被訪問(wèn),用于緩存變量(只讀);
□累加器:只能用來(lái)做加法的變量,如計(jì)數(shù)和求和。
4.4.1 廣播變量
廣播變量允許程序員保留一個(gè)只讀的變量,緩存在每一臺(tái)Worker節(jié)點(diǎn)的Cache,而不是每個(gè)Task發(fā)送一份副本。例如,可以給每個(gè)Worker節(jié)點(diǎn)設(shè)置一個(gè)輸入數(shù)據(jù)集副本,Spark會(huì)嘗試使用一種高效的廣播算法傳播廣播變量,從而減少通信的代價(jià)。
廣播變量是通過(guò)調(diào)用SparkContext.broadcast(v)方法從變量v創(chuàng)建的,廣播變量是一個(gè)v的封裝,它的值可以通過(guò)調(diào)用value方法獲得,代碼如下:
? ? ? ? ? ? scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)
在廣播變量被創(chuàng)建后,可以在集群運(yùn)行的任何函數(shù)中代替v值被調(diào)用,由于v值在第一次調(diào)用后緩存到任務(wù)節(jié)點(diǎn),重復(fù)調(diào)用時(shí)不需要被再次傳遞到這些節(jié)點(diǎn)上。另外,對(duì)象v不能在廣播后修改,這樣可以保證所有節(jié)點(diǎn)收到相同的廣播值。
4.4.2 累加器
累加器是一種只能通過(guò)關(guān)聯(lián)操作進(jìn)行“加”操作的變量,因此可以在并行計(jì)算中得到高效的支持。類似MapReduce中的counter,可以用來(lái)實(shí)現(xiàn)計(jì)數(shù)和求和等功能。Spark原生支持Int和Double類型的累加器,程序員可以自己添加新的支持類型。
累加器可以通過(guò)調(diào)用SparkContext.accumulator(v)方法從一個(gè)初始值v中創(chuàng)建。運(yùn)行在集群上的任務(wù),可以通過(guò)使用+=進(jìn)行累加,但是不能進(jìn)行讀取。只有主程序可以使用value的方法讀取累加器的值。
下面的代碼展示了如何利用累加器,將一個(gè)數(shù)組里面的所有元素相加。
? ? ? ? ? ? scala> val accum = sc.accumulator(0)
accum: org.apache.spark.Accumulator[Int] = 0
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)
...
*** INFO scheduler.DAGScheduler: Stage 0 finished in 0.111 s
*** INFO spark.SparkContext: Job finished took 0.288603412 s
scala> accum.value
res1: Int = 10
當(dāng)然,這段代碼使用的是累加器內(nèi)置支持的Int類型,程序員也可以通過(guò)創(chuàng)建AccumulatorParam的子類來(lái)創(chuàng)建自己的類型。該AccumulatorParam接口有兩個(gè)方法:提供了一個(gè)“zero”值進(jìn)行初始化,以及一個(gè)addInPlace方法將兩個(gè)值相加,如果需要可以自己嘗試需要的類型,如Vector。
4.5 本章小結(jié)
總之,RDD是Spark的核心,也是整個(gè)Spark的架構(gòu)基礎(chǔ)。RDD是在集群應(yīng)用中分享數(shù)據(jù)的一種高效、通用、容錯(cuò)的抽象,是由Spark提供的最重要的抽象的概念,它是一種有容錯(cuò)機(jī)制的特殊集合,可以分布在集群的節(jié)點(diǎn)上,以函數(shù)式編程操作集合的方式,進(jìn)行各種并行操作。
本章重點(diǎn)講解了如何創(chuàng)建Spark的RDD,以及RDD的一系列轉(zhuǎn)換和執(zhí)行操作,并給出一些基于Scala編程語(yǔ)言的支持。并對(duì)廣播變量和累加器兩種模式的共享變量進(jìn)行了講解,但是在此僅僅講解了RDD的基礎(chǔ)相關(guān)部分,對(duì)RDD在執(zhí)行過(guò)程中的依賴轉(zhuǎn)換,以及RDD的可選特征優(yōu)先計(jì)算位置(preferred locations)和分區(qū)策略,并沒(méi)有進(jìn)行詳細(xì)描述,在后面的章節(jié)中會(huì)結(jié)合實(shí)例對(duì)此進(jìn)行重點(diǎn)講述。