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
}