SparkCoreインタビューの質問

1. WordCountを書き込み(ローカルファイルを読み取り)、クラスターにパックして実行し、追加する必要のある主なパラメーターを説明します。

コード

	val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName)
    val sc = new SparkContext(conf)
    val resRDD: RDD[(String, Int)] = sc.textFile(args(0)).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
    resRDD.saveAsTextFile(args(1))

    sc.stop()

jarコマンドを実行します。

bin/spark-submit \
--master yarn \
--deploy-mode client \
--executor-cores 2 \
--class com.aura.spark.day06.Spark09_Interview1 \
/home/hadoop/jar/WordCount.jar \
/word_in \
/word_out

コマンドの解析

  • master:プログラムの動作モードを指定します。
  • deploy-mode:ドライバーをクラスターで実行するかローカルで実行するかを指定します。
  • executor-cores:各エグゼキューターのコア数。
  • class:Sparkプログラムのメインクラスの完全修飾名。

2.RDDの5つの特徴

  • パーティションのセット。
  • 各パーティションデータの計算ルール。
  • 他のRDDとの血液依存関係;
  • KVタイプで保存されたデータの場合、オプションのパーティショナーがあります。
  • スライスの優先順位のリストを保存します。

3. RDDの作成方法、たとえば、いくつかの方法があります。

4種類。

  • MakeRDD()またはparallelize()メソッドを使用してRDDを作成します。
  • textFile()は、ファイルを読み取ってRDDを作成します。
  • 変換演算子は、他のRDDを介して新しいRDDを生成します。
  • new JdbcRDD()などの新しいメソッドを使用してRDDを作成します。

4. RDDを作成して、パーティションのデータを文字列に変換します。例:(Array( "a"、 "b"、 "c"、 "d")、2)=>( "ab"、 "cd")

	val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.makeRDD(Array("a", "b", "c", "d"), 2)
    // 将同一分区数据转为数组
    val glomRDD: RDD[Array[String]] = rdd.glom()
    glomRDD.foreach(println)
    val resRDD: RDD[String] = glomRDD.map(
      arr => {
    
    
        arr.mkString("")
      }
    )
    resRDD.foreach(println)

    sc.stop()

5.mapとmapPartitionsの違い

  • mapは、指定されたルールに従ったRDD内の要素の1対1のマッピングです。
  • mapPartitonsは、パーティションの単位で指定されたルールに従ってRDDの要素をマップします。

6.合体と再分割の2つのオペレーターの役割、違い、つながり

効果

        再パーティション。

連絡先

        再パーティションの最下層は合体を呼び出しますが、シャッフルパラメータがtrueに設定されているため、シャッフル再パーティションが実行されます。

違い

        合体のデフォルトのシャッフルパラメータはfalseです。これは、シャッフルの再パーティション化が実行されないことを意味し、通常、パーティションを減らすために使用されます。再パーティション化は通常、パーティションを増やすために使用されます。

7. zip演算子を使用する際に注意すべき点(つまり、使用できない状況)

  • Scalaコレクションでは、2つのコレクションの要素数が同じでなくても、zip関数はzip操作を実行できますが、余分な要素は返されません。
  • ただし、Spark RDD演算子では、zip演算子は同じ数のパーティション要素を持つ2つのRDDのみ操作できます

8.reduceByKeyとgroupByKeyの違い

  • reduceByKey:キーに従って集計します。シャッフルする前に、パーティション内のデータは事前に集計され、返される結果はkvタイプRDD(RDD [K、V])です。
  • groupByKey:キーグループ化し直接シャッフルを実行します返される結果は、k-iteratorタイプのRDD(RDD [K、Iterable [V]])です。
  • ビジネスロジックに影響を与えることなく、reduceByKeyが推奨されます。合計操作はビジネスロジックに影響を与えず、平均化操作はビジネスロジックに影響を与えます。

9. reduceByKey、aggregateByKey、foldByKeyの違いと接続

連絡先

  • すべての集計操作はキーに従って実行され、データはシャッフルの前に事前に集計されます。

違い

  • reduceByKey:初期値はなく、パーティション内およびパーティション間の計算ルールは同じです。
  • AggregateByKey:初期値があり、初期値はパーティション内およびパーティション間の計算に関与します。パーティション内およびパーティション間のルールは一貫していない可能性があります。
  • foldByKey:初期値があり、初期値はパーティション内の計算に関与します。パーティション内およびパーティション間の計算ルールは同じです。

10. CombineByKeyのパラメーター関数、そのパラメーター呼び出しのタイミングを説明する

  • パラメータ1:データの構造を変換できます。
  • パラメータ2:パーティション内の計算ルール。
  • パラメータ3:パーティション間の計算ルール。

11.RDDを使用して結合を実装する複数の方法

  • rdd1.join(rdd2):同じキーに対応する値を関連付けます。キーが特定のRDDのみの場合、そのキーは返されません。
  • rdd1.leftOuterJoin(rdd2):rdd1のすべてのキー値と関連するキー値を返します。
  • rdd1.rightOuterJoin(rdd2):rdd2のすべてのKey-Valueと関連するKey-Valueを返します。
  • rdd1.cogroup(rdd2):各rddは、最初に独自のキーを関連付けてセットを形成し、次にマージします。

12.aggregateByKeyとaggregateの違いと接続

連絡先

  • どちらもパーティション内およびパーティション間の要素に対して集計操作を実行し、両方とも初期値を持っています。

違い

  • AggregateByKeyは、タイプkvのRDDを操作する変換演算子です。初期値は、パーティション内およびパーティション間の計算に関与します。初期値は、RDD内の各要素で反復および操作されます。
  • Aggregateはアクション演算子です。初期値はパーティション内およびパーティション間の計算に関与します。パーティション内で計算する場合、RDDの各パーティションの初期値と要素は、指定されたルールに従って計算されます。パーティション間で計算する場合、初期値は一度だけ計算に参加します。

13. RDDを作成し、パーティションルールをカスタマイズして実装しますか?Sparkを値で分割できますか

カスタムパーティショナー

object Spark12_Interview13 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[(String, String)] = sc.makeRDD(List(("13698624174", "河北"), ("13766887551", "广东"),
      ("13876543211", "上海"), ("17677885551", "河南")), 2)
    // 使用自定义分区器重新分区
    val resRDD: RDD[(String, String)] = rdd.partitionBy(new MyPartitoner(4))
    resRDD.mapPartitionsWithIndex{
    
    
      case (index, datas) => {
    
    
        println(index + "--->" + datas.mkString(","))
        datas
      }
    }.collect()
    sc.stop()
  }
}

// 自定义分区器
class MyPartitoner(partitons: Int) extends Partitioner {
    
    
  // 分区数量
  override def numPartitions: Int = partitons

  // 自定义分区逻辑
  override def getPartition(key: Any): Int = {
    
    
    val strKey: String = key.asInstanceOf[String]
    // 返回分区号
    if (strKey.startsWith("136")) 0
    else if (strKey.startsWith("137")) 1
    else if (strKey.startsWith("138")) 2
    else 3
  }
}

値による分割

  • RDDは、マップマッピングを通じて値とキーの位置を交換します。

14.ファイルを読み取り、WordCount関数を実現します。(少なくとも3つの方法で、さまざまな演算子を使用して達成します)

方法1

    val rdd: RDD[String] = sc.textFile("D:\\develop\\workspace\\bigdata2021\\spark2021\\input\\word.txt")
     // 方式一
    val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)

方法2

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).groupByKey().map {
    
    
      case (key, datas) => {
    
    
        (key, datas.size)
      }
   	}

方法3

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).groupBy(word => word).map {
    
    
      case (key, datas) => {
    
    
        (key, datas.size)
      }
    }

方法4

    val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).aggregateByKey(0)(_+_,_+_)

方法5

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).foldByKey(0)(_+_)

ウェイシックス

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).combineByKey(
      // 转换数据结构
      elem => elem,
      // 分区内计算规则
      (a: Int, b: Int) => {
    
    
        a + b
      },
      // 分区间计算规则
      (a: Int, b: Int) => {
    
    
        a + b
      }
    )

15.RDDの血縁関係についてのあなたの理解について教えてください

  • RDDは、変換演算子を使用して一連のRDDを生成します。Sparkは各RDD間の依存関係を記録ます。この関係は血縁関係と呼ばれます。toDebugString演算子を使用してRDD間の血縁関係を表示できます。
  • Sparkは、RDD間の血液関係に基づいてDAG有向非巡回グラフ形成します。この有向非巡回グラフによれば、Sparkはフォールトトレランスとデータ回復を効率的に処理できます

16.Sparkはタスクのセグメンテーションをどのように実行しますか。関連する概念を説明してください。

  • アプリケーション:sparkプログラムはアプリケーションであり、アプリケーションは複数のジョブを持つことができます。
  • ジョブジョブ:アクションオペレーターのトリガーはジョブであり、ジョブには複数のステージがあります。
  • ステージステージ:幅広い依存関係を持つさまざまなステージを分割します。ステージ数=広い依存関係の数+1。ステージには複数のタスクを含めることができます。
  • タスクタスク:各ステージの最後のRDDのパーティションの数=タスクの数。

17.RDDキャッシュとcheckPointの違いと接続

連絡先

  • cacheとcheckPointはどちらもRDDにデータをキャッシュし、同じ計算ロジックを持つRDDは、再計算せずにキャッシュからデータを直接フェッチできます。
  • アクション演算子がトリガーされた場合にのみ、キャッシュは実際にキャッシュされます。

違い

  • キャッシュはRDDの血縁関係を断ち切ることはありません。キャッシュはデフォルトでメモリに保存され、ローカルディスクに保存するように設定できます。ただし、キャッシュによってキャッシュされたデータは、プログラムの実行時に失われます。
  • チェックポイントチェックポイントは、RDDの血縁関係を遮断し、HDFSなどの高可用性および高信頼性のストレージシステムにデータを保存できます。
  • データの正確性を確保するために、チェックポイントを初めて使用するときは、RDDの血縁関係に従って最初から最後まで実行されます。
  • 通常、チェックポインとキャッシュは一緒に使用されます。

18.Sparkは、HDFSファイルのデフォルトのスライスメカニズムを読み取ります

  • RDDを作成するとき、ファイルパスとパーティションの最小数minPartitionsがHadoopRDDに渡されます。
    ここに画像の説明を挿入

  • FileInputFormatのgetSplitsメソッドを呼び出して、スライス情報を計算します。
    ここに画像の説明を挿入

  • まず、ターゲットスライスサイズgoalSizeを計算します。ターゲットスライスサイズ=ファイルサイズ/パーティションの最小数。
    ここに画像の説明を挿入

  • 次に、最小スライスminSizeを計算します:max(FileInputFormat.SPLIT_MINSIZE、minSplitSize)、最小は1です。
    ここに画像の説明を挿入

  • 次に、splitSize = Math.max(minSize、Math.min(goalSize、blockSize))を計算します。したがって、SplitSizeは通常、goalSizeとblockSizeの最小値です。
    ここに画像の説明を挿入
    ここに画像の説明を挿入

  • 残りのファイルサイズ/ splitSize> 1.1の場合は、1つのスライスを切り取ります。
    ここに画像の説明を挿入

19.放送変数についてのあなたの理解について教えてください

  • ブロードキャスト変数:分散共有読み取り専用変数。
  • Sparkプログラムの実行中、関数はオペレーターに渡されます。関数は、ドライバー側で定義された変数にアクセスする必要がある場合があります。Sparkは、エグゼキューター側のタスクごとにこの変数のコピーを送信します。変数が比較的大きいと消費されます。エグゼキュータ側のメモリが増えます。
  • 変数をブロードキャスト変数として定義すると、Sparkは、すべてのタスクがアクセスできるように、ブロードキャスト変数のコピーのみをエグゼキューター側に送信します。

20.カウント機能を実現するためにアキュムレータをカスタマイズする

Hで始まる単語の数を数える

object Spark14_Interview20 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "HaHa", "spark", "scala", "java", "Hive", "Hi"), 2)
    // 声明累加器
    val acc = new MyAccumulator20
    // 注册
    sc.register(acc, "myacc")
    // 使用
    rdd.foreach(datas => {
    
    
      acc.add(datas)
    })
    // 打印累加器中的值
    println(acc.value)

    sc.stop()
  }
}

// 自定义累加器  泛型为输入类型和输出类型
class MyAccumulator20 extends AccumulatorV2[String, Int] {
    
    
  var num = 0

  // 判断是否为初始值
  override def isZero: Boolean = {
    
    
    num == 0
  }

  // 复制累加器
  override def copy(): AccumulatorV2[String, Int] = {
    
    
    val MyAcc = new MyAccumulator20
    MyAcc.num = this.num
    MyAcc
  }

  // 重置累加器
  override def reset(): Unit = {
    
    
    num = 0
  }

  // 添加元素
  override def add(v: String): Unit = {
    
    
    if (v.startsWith("H")) {
    
    
      num += 1
    }
  }

  // 合并累加器
  override def merge(other: AccumulatorV2[String, Int]): Unit = {
    
    
    num = this.num + other.value
  }

  // 取值
  override def value: Int = num
}

おすすめ

転載: blog.csdn.net/FlatTiger/article/details/115136126