スパークコードの読みやすさとパフォーマンスの最適化 - の典型的な5(ハッシュ結合)

スパークコードの読みやすさとパフォーマンスの最適化 - の典型的な5(ハッシュ結合)

1.コンテンツの概要ポイント

  • データセットの間に参加
  • 大規模なデータセット、小さなデータセット
  • データスキュー
  • シャッフルを削減
  • より良い文言flatMap +いくつかの+なし
  • 誤用ブロードキャスト一般的な落とし穴、
    * 注:前の記事コンテンツのヒントを重複していないが、直接変更されました

2.元のコード

import org.apache.spark.{SparkConf, SparkContext}


object HashJoin {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("HashJoin").setMaster("local[4]")
    val sc = new SparkContext(conf)

    val smallRDD = sc.parallelize(
      Seq((1, 'a'), (2, 'a'), (3, 'y'), (4, 'a')),
      4
    )

    val largeRDD = sc.parallelize(
      for (x <- 1 to 10000) yield (x % 4, x),
      4
    )

    // 对2份数据做join
    val joined = smallRDD.join(largeRDD )
    joined.collect().foreach(println)

    sc.stop();
  }
}

3.コードのパフォーマンス、最適化の読みやすさ

  • 小規模および大規模なデータセットは、データセットには、以下の具体的な例を、小さなデータセットの後ろに、目の前にあるように、大規模なデータセットを結合します:
    val joined = largeRDD.join(smallRDD)
    joined.collect().foreach(println)
    
  • コンピューティングに参加大量のデータをやって、あなたはそうするために上記のようなことがあり、そして最後のいくつかのパーティションは、コンピューティング操作に参加(WTF?)非常に、非常に遅いデータスキューが発生したためです。そして、あなたは演算子をマッピングする大規模なデータセットにデータ放送の小さなセットを使用することができ、シャッフルの減少は、データスキューの問題が原因解消します!例としては、次のとおりです:
    1. 執筆:マップ+ fliter(一般)
    // 将小部分数据集collect到Driver端,转为Map,然后以广播的形式分发到各个Executor
    val smallMap = smallRDD.collect().toMap
    val smallBroadcast = sc.broadcast(smallMap)
    
    val joined = largeRDD.map { case (key, value) =>
      // 从广播中获取value
      val smallValue: Option[Char] = smallBroadcast.value.get(key)
      (key, (value, smallValue))
    }.filter(_._2._2.isDefined) // 过滤掉无效数据
    
    joined.collect().foreach(println)
    
    1. 両者を書き込む:(典型的には、しかし、複数の点の場合にも適用)+ ListBufferをマッピング
    val smallMap = smallRDD.collect().toMap
    val smallBroadcast = sc.broadcast(smallMap)
    
    val joined = largeRDD.flatMap { case (key, value) =>
      // 提前准备一个Buffer
      val buffer = ListBuffer[(Int, (Int, Char))]()
      val smallValue: Option[Char] = smallBroadcast.value.get(key)
      // 存在数据时,将数据放入Buffer
      if (smallValue.isDefined)
        buffer.append((key, (value, smallValue.get)))
      // 返回Buffer
      buffer
    }
    
    joined.collect().foreach(println)
    
    1. 3著:flatMap +いくつかの+なし(ベスト)
    val smallMap = smallRDD.collect().toMap
    val smallBroadcast = sc.broadcast(smallMap)
    
    val joinedRDD = largeRDD.flatMap { case (key, value) =>
      // 利用匹配模式,可读性较高
      smallBroadcast.value.get(key) match {
          // 返回类型为Option,为None的将被flatMap打平,从而过滤掉
        case Some(v) => Some((key, (value, v)))
        case None => None
      }
    }
    joinedRDD.collect().foreach(println)
    
  • 一般的な誤用、トラップ
    1. 共通誤操作:容器チェック値をトラバースする方法(以下、エラーの一例です)
    val small = smallRDD.collect()
    val smallBroadcast = sc.broadcast(small)
    
    val joined = largeRDD.flatMap { case (key, value) =>
      // 使用容器的find找到key相等的值,将会遍历容器,效率较差
      // 正确的做法:使用HashMap,利用hash算法根据key查找value,效率更高
      smallBroadcast.value
        .find(_._1 == key) match {
        case Some(v) => Some((key, (value, v)))
        case None => None
      }
    }
    
    1. 一般的な誤用:データ型マッピング変換放送、クラスのflatMapオペレータ(以下、エラーの一例です)
    val small = smallRDD.collect()
    val smallBroadcast = sc.broadcast(small)
    
    val joined = largeRDD.flatMap { case (key, value) =>
      // 在RDD的map、flatMap等算子中,将广播再次转换为其他数据(此处是toMap)
      // RDD中由多少条数据,这个转换就会被执行多少次,对性能影响极大
      // 正确做法:应该在广播前,将需要广播的数据转换好
      smallBroadcast.value.toMap
        .get(key) match {
        case Some(v) => Some((key, (value, v)))
        case None => None
      }
    }
    
    1. よくある落とし穴:キー、元のデータが一意ではありません(以下は正しい例です)
    // 原数据key不唯一
    val smallRDD = sc.parallelize(
     Seq((1, 'a'), (1, 'c'), (2, 'a'), (3, 'x'), (3, 'y'), (4, 'a')),
     4
    )
    
    val largeRDD = sc.parallelize(
     for (x <- 1 to 10000) yield (x % 4, x),
     4
    )
    
    // 当原数据的key不唯一时,应该提前分组
    val smallMap = smallRDD.groupByKey()
     .collect()
     .toMap
    val smallBroadcast = sc.broadcast(smallMap)
    
    val joinedRDD = largeRDD.flatMap { case (key, value) =>
     smallBroadcast.value.get(key) match {
       case Some(iter) => iter.map(v => (key, (value, v)))
       case None => Iterable[(Int, (Int, Char))]()
     }
    }
    joinedRDD.collect().foreach(println)
    
  • 注意:
    • )smallRDD.collectを(対応するために、データ放送、必要ドライバー、Executorの十分なメモリを使用します
    • ドライバー、エグゼキュータが収集小さなデータセットのサイズを収容するのに十分なメモリでない場合は、あなたはまだだけオペレータに参加行きます
    • もちろん、出会いの放送データセットはあなたにも分けn回はマップが参加すること、nは株式にデータセットを放送することができ、大きすぎて、十分なメモリが利用可能です。

最終的な4:最適化されたコード+ノート

import org.apache.spark.{SparkConf, SparkContext}

object HashJoin2 {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("HashJoin").setMaster("local[4]")
    val sc = new SparkContext(conf)

	// 原数据的key不唯一
    val smallRDD = sc.parallelize(
      Seq((1, 'a'), (1, 'c'), (2, 'a'), (3, 'x'), (3, 'y'), (4, 'a')),
      4
    )

    val largeRDD = sc.parallelize(
      for (x <- 1 to 10000) yield (x % 4, x),
      4
    )


    // 利用广播小数据集到大数据集的map算子中,减少一次shuffle,消灭数据倾斜造成的问题
    // 当本地内存足以容纳smallRDD.collect()后的数据时,可以这样做

    // 将小部分数据集collect到Driver端,转为Map,然后以广播的形式分发到各个Executor
    // 考虑当原数据的key不唯一时,应该提前分组
  	val smallMap = smallRDD.groupByKey()
	    .collect()
	    .toMap
    val smallBroadcast = sc.broadcast(smallMap)

    val joinedRDD = largeRDD.flatMap { case (key, value) =>
    	// 利用匹配模式,可读性较高
      smallBroadcast.value.get(key) match {
      	 // 返回类型为Option,为None的将被flatMap打平,从而过滤掉
        case Some(iter) => iter.map(v => (key, (value, v)))
        case None => Iterable[(Int, (Int, Char))]()
      }
    }
    
    joinedRDD.collect().foreach(println)
    
    sc.stop();
  }
}
公開された128元の記事 ウォン称賛45 ビュー15万+

おすすめ

転載: blog.csdn.net/alionsss/article/details/89529161