spark學習筆記--RDD鍵對操作

zxrui發表於2018-07-06

RDD鍵值對操作

建立pair RDD

scala:

val pairs = lines.map(x => (x.split(" ")(0), x))

轉化操作

Pair RDD的轉化操作(以鍵值對集合{(1, 2), (3, 4), (3, 6)}為例) enter image description here enter image description here

針對兩個pair RDD的轉化操作(rdd = {(1, 2), (3, 4), (3, 6)}other = {(3, 9)}) enter image description here

scala:

pairs.filter{case (key, value) => value.length < 20}

1. 聚合操作(轉化操作!)

類似於轉化操作,reducebykey(),foldbykey(),combinebykey()
scala:

rdd.mapValues(x => (x, 1)).reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2))

scala:

val result = input.combineByKey((v) => (v, 1),(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
.map{ case (key, value) => (key, value._1 / value._2.toFloat) }
result.collectAsMap().map(println(_))

mapvalues(func)可以將rdd轉成pairrdd

  • 並行度調優

Method1: 通過分組或聚合的動作

scala:

val data = Seq(("a", 3), ("b", 4), ("a", 1))  
sc.parallelize(data).reduceByKey((x, y) => x + y)    // 預設並行度
sc.parallelize(data).reduceByKey((x, y) => x + y, 10)    // 自定義並行度

Method2: 分割槽操作

  • repartition()

    它會把資料通過網路進行混洗,並建立出新的分割槽集合。切記,對資料進行重新分割槽是代價相對比較大的操作

  • coalesce()

    repartition()的優化版本

2.資料分組

  • groupByKey()(對單個RDD)

    對於一個由型別 K 的鍵和型別 V 的值組成的 RDD,所得到的結果 RDD 型別會是 [K, Iterable[V]]。

  • cogroup()(對多個RDD)

    cogroup() 不僅可以用於實現連線操作,還可以用來求鍵的交集。cogroup()對兩個鍵的型別均為 K 而值的型別分別為 V 和 W 的 RDD 進行 cogroup() 時,得到的結果 RDD 型別為 [(K, (Iterable[V], Iterable[W]))]

3.連線

  • join()

scala:

val storeAddress = sc.parallelize(Seq(
   (Store("Ritual"), "1026 Valencia St"), (Store("Philz"), "748 Van Ness Ave"),
   (Store("Philz"),"3101 24th St"), (Store("Starbucks"), "Seattle")))
val storeRating = sc.parallelize(Seq(
   (Store("Ritual"), 4.9), (Store("Philz"), 4.8)))
storeAddress.join(storeRating)

join(內連線),leftOuterJoin(other),rightOuterJoin(other)

4.排序

  • sortbykey()

scala:

val input: RDD[(Int, Venue)] = ...
implicit val sortIntegersByString = new Ordering[Int] {
override def compare(a: Int, b: Int) = a.toString.compare(b.toString)
}
rdd.sortByKey()

排序引數,assending(預設)
當把資料排好序後,後續對資料進行 collect() 或 save() 等操作都會得到有序的資料。

Pair RDD的行動操作

Pair RDD的行動操作(以鍵值對集合{(1, 2), (3, 4), (3, 6)}為例) enter image description here

資料分割槽

在分散式程式中,通訊的代價是很大的,因此控制資料分佈以獲得最少的網路傳輸可以極大地提升整體效能,spark可以通過控制RDD的分割槽來優化通訊開銷

enter image description here

如上圖所示,userData表 join events表

Q: 預設情況下,連線操作會將兩個資料集中的所有鍵的雜湊值都求出來,將該雜湊值相同的記錄通過網路傳到同一臺機器上,然後在那臺機器上對所有鍵相同的記錄進行連線操作
A: 在程式開始時,對 userData 表使用 partitionBy() 轉化操作,將這張錶轉為雜湊分割槽。可以通過向 partitionBy 傳遞一個 spark.HashPartitioner 物件來實現該操作

scala:

val sc = new SparkContext(...)
val userData = sc.sequenceFile[UserID, UserInfo]("hdfs://...")
             .partitionBy(new HashPartitioner(100))   // 構造100個分割槽
             .persist()

1.partitionBy() 是一個轉化操作,因此它的返回值總是一個新的 RDD,但它不會改變原來的 RDD。RDD 一旦建立就無法修改。因此應該對 partitionBy() 的結果進行持久化,並儲存為 userData,而不是原來的 sequenceFile() 的輸出
2.傳給 partitionBy() 的 100 表示分割槽數目,它會控制之後對這個 RDD 進行進一步操作(比如連線操作)時有多少任務會並行執行。總的來說,這個值至少應該和叢集中的總核心數一樣。
3. 如果沒有將 partitionBy() 轉化操作的結果持久化,那麼後面每次用到這個 RDD 時都會重複地對資料進行分割槽操作。不進行持久化會導致整個 RDD 譜系圖重新求值。那樣的話,partitionBy() 帶來的好處就會被抵消,導致重複對資料進行分割槽以及跨節點的混洗,和沒有指定分割槽方式時發生的情況十分相似。

  • 自定義分割槽(partitioner())

scala:

class DomainNamePartitioner(numParts: Int) extends Partitioner {
  override def numPartitions: Int = numParts
  override def getPartition(key: Any): Int = {
  val domain = new Java.net.URL(key.toString).getHost()
  val code = (domain.hashCode % numPartitions)
  if(code < 0) {
    code + numPartitions // 使其非負
  }else{
    code
  }
}
// 用來讓Spark區分分割槽函式物件的Java equals方法
override def equals(other: Any): Boolean = other match {
  case dnp: DomainNamePartitioner =>
    dnp.numPartitions == numPartitions
  case _ =>
    false
  }
}

numPartitions: Int:返回建立出來的分割槽數。
getPartition(key: Any): Int:返回給定鍵的分割槽編號(0 到 numPartitions-1)。
equals():Java 判斷相等性的標準方法。這個方法的實現非常重要,Spark 需要用這個方法來檢查你的分割槽器物件是否和其他分割槽器例項相同,這樣 Spark 才可以判斷兩個 RDD 的分割槽方式是否相同。

相關文章