spark學習筆記--進階程式設計

zxrui發表於2018-07-09

進階程式設計

共享變數

通常在向 Spark 傳遞函式時,比如使用 map() 函式或者用 filter() 傳條件時,可以使用驅動器程式中定義的變數,但是叢集中執行的每個任務都會得到這些變數的一份新的副本,更新這些副本的值也不會影響驅動器中的對應變數。Spark 的兩個共享變數,累加器與廣播變數,分別為結果聚合與廣播這兩種常見的通訊模式突破了這一限制。

累加器

在scala中累加空行

val sc = new SparkContext(...)
val file = sc.textFile("file.txt")

val blankLines = sc.accumulator(0) // 建立Accumulator[Int]並初始化為0

val callSigns = file.flatMap(line => {
if (line == "") {
    blankLines += 1 // 累加器加1
}
line.split(" ")
})

callSigns.saveAsTextFile("output.txt")
println("Blank lines: " + blankLines.value)
  • 總結起來,累加器的用法如下所示:

    • 通過在驅動器中呼叫 SparkContext.accumulator(initialValue) 方法,建立出存有初始值的累加器。返回值為 org.apache.spark.Accumulator[T] 物件,其中 T 是初始值 initialValue 的型別。

    • Spark 閉包裡的執行器程式碼可以使用累加器的 += 方法(在 Java 中是 add)增加累加器的值。

    • 驅動器程式可以呼叫累加器的 value 屬性(在 Java 中使用 value() 或 setValue())來訪問累加器的值。

注意:工作節點上的任務不能訪問累加器的值。從這些任務的角度來看,累加器是一個只寫變數。在這種模式下,累加器的實現可以更加高效,不需要對每次更新操作進行復雜的通訊。

  • 累加器容錯性

    • Spark 會自動重新執行失敗的或較慢的任務來應對有錯誤的或者比較慢的機器。

      例如:
      1.如果對某分割槽執行map()操作的節點失敗了,Spark會在另一個節點上重新執行該任務。
      2.即使該節點沒有崩潰,而只是處理速度比別的節點慢很多,Spark也可以搶佔式地在另一個節點上啟動一個任務副本,如果該任務更早結束就可以直接獲取結果。
      3.即使沒有節點失敗,Spark有時也需要重新執行任務來獲取快取中被移除出記憶體的資料。
      因此最終結果就是同一個函式可能對同一份資料執行了多次,這取決於叢集的動態。

    • 對於要在行動操作中使用的累加器,Spark只會把每個任務對各累加器的修改應用一次。因此,如果想要一個無論在失敗還是重複計算時都絕對可靠的累加器,我們必須把它放在 foreach() 這樣的行動操作中。

    • 對於在 RDD 轉化操作中使用的累加器,就不能保證有這種情況了。轉化操作中累加器可能會發生不止一次更新。在轉化操作中,累加器通常只用於除錯目的。

  • 廣播的優化

    • 當廣播一個比較大的值時,選擇既快又好的序列化格式是很重要的,因為如果序列化物件的時間很長或者傳送花費的時間太久,這段時間很容易就成為效能瓶頸,可以使用spark.serializer

廣播變數

它可以讓程式高效地向所有工作節點傳送一個較大的只讀值,以供一個或多個 Spark 操作使用。

scala:

// 查詢RDD contactCounts中的呼號的對應位置。將呼號字首
// 讀取為國家程式碼來進行查詢
val signPrefixes = sc.broadcast(loadCallSignTable())
val countryContactCounts = contactCounts.map{case (sign, count) =>
val country = lookupInArray(sign, signPrefixes.value)
    (country, count)
}.reduceByKey((x, y) => x + y)
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt")

基於分割槽進行操作

基於分割槽對資料進行操作可以讓我們避免為每個資料元素進行重複的配置工作。
Spark 提供基於分割槽的 map 和 foreach,讓你的部分程式碼只對 RDD 的每個分割槽執行一次,這樣可以幫助降低這些操作的代價。

enter image description here

與外部程式間的管道

有三種可用的語言供你選擇,這可能已經滿足了你用來編寫 Spark 應用的幾乎所有需求。Spark 在 RDD 上提供 pipe() 方法。

例如Spark與R語言的結合

R語言:

#!/usr/bin/env Rscript
library("Imap")
f <- file("stdin")
open(f)
while(length(line <- readLines(f,n=1)) > 0) {
# 處理行
contents <- Map(as.numeric, strsplit(line, ","))
mydist <- gdist(contents[[1]][2], contents[[1]][2],
                 contents[[1]][3], contents[[1]][4],
                 units="m", a=6378137.0, b=6356752.3142, verbose = FALSE)
write(mydist, stdout())
}

在scala中使用pipe()呼叫R檔案:

// 使用一個R語言外部程式計算每次呼叫的距離
// 將指令碼新增到各個節點需要在本次作業中下載的檔案的列表中
val distScript = "./src/R/finddistance.R"
val distScriptName = "finddistance.R"
sc.addFile(distScript)
val distances = contactsContactLists.values.flatMap(x => 
x.map(y =>s"$y.contactlay,$y.contactlong,$y.mylat,$y.mylong"))
.pipe(Seq(SparkFiles.get(distScriptName)))
println(distances.collect().toList)

SparkContext.addFile(path),可以構建一個檔案列表,讓每個工作節點在 Spark 作業中下載列表中的檔案。
SparkFiles.get(Filename)來定位單個檔案

  • RDD的pipe()方法讓RDD容易通過指令碼管道
    • rdd.pipe(Seq(SparkFiles.get("finddistance.R"), ","))
    • rdd.pipe(SparkFiles.get("finddistance.R") + " ,")

數值RDD的操作

Spark 的數值操作是通過流式演算法實現的,允許以每次一個元素的方式構建出模型。這些統計資料都會在呼叫 stats() 時通過一次遍歷資料計算出來,並以 StatsCounter 物件返回。

enter image description here

Scala移除異常值:

// 現在要移除一些異常值,因為有些地點可能是誤報的
// 首先要獲取字串RDD並將它轉換為雙精度浮點型
val distanceDouble = distance.map(string => string.toDouble)
val stats = distanceDoubles.stats()
val stddev = stats.stdev
val mean = stats.mean
val reasonableDistances = distanceDoubles.filter(x => math.abs(x-mean) < 3 * stddev)
println(reasonableDistance.collect().toList)

相關文章