高度なRDD(第3章)
ワードカウントを分析する
sc.textFile("hdfs:///demo/words/t_word") //RDD0
.flatMap(_.split(" ")) //RDD1
.map((_,1)) //RDD2
.reduceByKey(_+_) //RDD3 finalRDD
.collect //Array 任务提交
RDDの特徴は何ですか?
RDDにはパーティションがあります-パーティションの数はRDDの数と同じです。
各パーティションは独立して操作され、パーティションの局所性の計算を可能な限り実現します
。RDDは読み取り専用のデータセットであり、RDDとRDDの間には相互依存関係があります。
Key-Value RDDの場合、パーティション戦略を指定できます[オプション]
データの場所に基づいて、局所性の計算を実現するのに最適な場所を選択します[オプション]
RDDフォールトトレランス
DAGScheduleが状態をどのように分割するかを理解するための前提条件は、通常RDDシステムと呼ばれる専門用語の系統を理解することです。
RDDは不変の読み取り専用コレクションであるため、Sparkの計算の本質は、RDDでさまざまな変換を実行することです。したがって、各変換では、この変換の入力として前のRDDが必要です。したがって、RDDの系統はRDD間の相互依存性を表します。
Sparkは、RDD間の関係を広い依存関係と狭い依存関係に分類します。
Sparkは、Lineageによって保存されたRDDの依存関係に基づいて、RDD計算のフォールトトレランスを実行します。先のSaprk前の時間フォールトトレランス政策を根据RDD依赖关系重新计算-⽆需⼲预
、RDD做Cache-临时缓存
、RDD做Checkpoint-持久化 ⼿段完成RDD计算的故障容错
。
RDDキャッシュ
キャッシュはRDD計算のフォールトトレランスのセクションです。RDDデータが失われると、プログラムはすべてのRDDを再計算せずに、キャッシュを介して現在のRDD値をすばやく計算できます。したがって、Sparkは特定のRDDを再計算する必要があります。複数使用する場合プログラムの実行効率を向上させるために、ユーザーはRDDキャッシュの使用を検討できます。
scala> var finalRDD=sc.textFile("hdfs:///demo/words/t_word").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
finalRDD: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[25] at reduceByKey at<console>:24
scala> finalRDD.cache //finalRDD.persist(StorageLevel.MEMORY_ONLY)
res7: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[25] at reduceByKey at<console>:24
scala> finalRDD.collect
res8: Array[(String, Int)] = Array((this,1), (is,1), (day,2), (come,1), (hello,1),(baby,1), (up,1), (spark,1), (a,1), (on,1), (demo,1), (good,2), (study,1))
scala> finalRDD.collect
res9: Array[(String, Int)] = Array((this,1), (is,1), (day,2), (come,1), (hello,1),
(baby,1), (up,1), (spark,1), (a,1), (on,1), (demo,1), (good,2), (study,1))
ユーザーはupersistメソッドを呼び出してキャッシュをクリアできます
scala> finalRDD.unpersist() //清空缓存中的finalRDD
res11: org.apache.spark.rdd.RDD[(String, Int)]
@scala.reflect.internal.annotations.uncheckedBounds = ShuffledRDD[25] at reduceByKey at<console>:24
Sparkでサポートされている以前のキャッシュスキームは次のとおりです。
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false) // 仅仅存储磁盘
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2) // 仅仅存储磁盘 存储两份
val MEMORY_ONLY = new StorageLevel(false, true, false, true) //缓存在内存中
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false) //先序列化再存储内存,费CPU节省内存
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true) //推荐使用
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
...
選び方は?
デフォルトでは、最高のパフォーマンスはもちろんMEMORY_ONLYですが、RDD全体のすべてのデータを格納するのに十分な大きさのメモリが必要であることが前提です。シリアル化および逆シリアル化操作を実行しないため、パフォーマンスオーバーヘッドのこの部分が回避されます。このRDDでの後続の計算操作は、純粋なメモリ内のデータに対する操作に基づいており、ディスクファイルから読み取る必要はありません。データのパフォーマンスは次のとおりです。また、非常に高く、データのコピーを作成して他のノードにリモートで送信する必要はありません。ただし、実際の実稼働環境では、この戦略を直接使用できるシナリオはまだ限られていることに注意する必要があります。RDDに大量のデータ(数億など)がある場合は、直接使用してください。この永続的な変更のレベルにより、JVMのOOMメモリオーバーフロー例外が発生します。
MEMORY_ONLYレベルの使用中にメモリオーバーフローが発生した場合は、MEMORY_ONLY_SERレベルの使用を試みることをお勧めします。このレベルでは、RDDデータがシリアル化され、メモリに保存されます。現時点では、各パーティションは単なるバイト配列であるため、オブジェクトの数が大幅に削減され、メモリ使用量が削減されます。MEMORY_ONLYよりも小さいこのレベルのパフォーマンスオーバーヘッドは、主にシリアル化と逆シリアル化のオーバーヘッドです。ただし、後続の計算は純粋なメモリに基づいて実行できるため、全体的なパフォーマンスは依然として比較的高くなります。また、発生する可能性のある問題は上記と同じです。RDDのデータ量が多すぎる場合でも、OOMメモリオーバーフローの例外が発生する可能性があります。
メモリで計算するために多くのコストが必要な場合、または大量のデータをフィルタリングして比較的重要な部分をメモリに保存できる場合を除いて、ディスクにリークしないでください。そうしないと、ディスクに保存されている計算速度が非常に遅くなり、パフォーマンスが大幅に低下します。
接尾辞は_2です。すべてのデータをコピーして他のノードに送信する必要があります。ジョブの高可用性が必要な場合を除き、データレプリケーションとネットワーク送信により、パフォーマンスのオーバーヘッドが大きくなります。それ以外の場合はお勧めしません。
チェック・ポイントのメカニズム
キャッシュメカニズムを使用してRDDの障害回復を効果的に保証することに加えて、キャッシュに障害が発生した場合でも、システムはRDDの結果を再計算します。したがって、RDD系統が比較的長い一部のシナリオでは、計算比率は時間です。 -消費し、ユーザーはチェックポイントメカニズムを使用してRDD計算結果を保存しようとすることができます。
このメカニズムとキャッシュの最も重要な違いは、チェックポイントを使用した後、チェックポイントのRDDデータがファイルシステムに直接保持されることです。通常、結果をhdfsに書き込むことをお勧めします。この種のチェックポイントは、自動的にクリアされません。
チェックポイントは、計算プロセス中に最初にRDDをマークし、タスクの実行が完了した後、マークのRDDにチェックポイントが実装されることに注意してください。つまり、マーク後のRDDの依存関係と結果を再計算する必要があります。 。
sc.setCheckpointDir("hdfs://CentOS:9000/checkpoints")
val rdd1 = sc.textFile("hdfs://CentOS:9000/demo/words/")
.map(line => {
println(line)
})
//对当前RDD做标记
rdd1.checkpoint()
rdd1.collect()
したがって、チェックポイントは通常、キャッシュと組み合わせて使用する必要があります。これにより、計算が1回保証されます。
sc.setCheckpointDir("hdfs://CentOS:9000/checkpoints")
val rdd1 = sc.textFile("hdfs://CentOS:9000/demo/words/")
.map(line => {
println(line)
})
rdd1.persist(StorageLevel.MEMORY_AND_DISK) //先cache,保证后面在checkpoint时不必重新进行RDD转换
//对当前RDD做标记
rdd1.checkpoint()
rdd1.collect()
rdd1.unpersist()//删除缓存
SparkRDD計算の詳細な説明
http://people.csail.mit.edu/matei/papers/2012/nsdi_spark.pdf
Sparkタスク計算のソースコードの分析
理論的ガイダンス
sc.textFile("hdfs:///demo/words/t_word") //RDD0
.flatMap(_.split(" ")) //RDD1
.map((_,1)) //RDD2
.reduceByKey(_+_) //RDD3 finalRDD
.collect //Array 任务提交
上記のコードを分析することにより、Sparkがタスク実行の初期段階でRDDの変換関係に従ってタスク実行DAGを形成することを見つけることは難しくありません。タスクをいくつかの段階に分割します。Sparkの最下層は、RDD間の依存関係の分割に基づいてステージに分割されます。Sparkは、RDDとRDD間の変換を次のように分類します。ShuffleDependency-widedependency|NarrowDependency-narrowdependency。SparkがRDDとRDDの間に狭い依存関係があることを検出すると、システムは、狭い依存関係を持つRDDの計算をAステージとして自動的に要約します。幅広い依存関係システムに遭遇し、新しいステージを開きます。
Sparkの広い依存性と狭い依存性の判断
広い依存関係:RDDの1つのパーティションは、サブRDDの複数のパーティションに対応し、フォークの発生は広い依存関係と見なされます。ShuffleDependency
狭い依存関係:RDDの1つのパーティション(複数のRDD)は、サブRDDの1つのパーティションにのみ対応し、狭い依存関係と見なされます。OneToOneDependency | RangeDependency | PruneDependency
タスク送信の初期段階で、Sparkは最初にすべての依存RDDと、finalRDDに基づいてRDD間の依存関係を取り消します。狭い依存関係が発生した場合、それらは現在の段階でマージされ、広い依存関係である場合、新しい段階が開かれます。
ソース追跡
getMissingParentStages
private def getMissingParentStages(stage: Stage): List[Stage] = {
val missing = new HashSet[Stage]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
val waitingForVisit = new ArrayStack[RDD[_]]
def visit(rdd: RDD[_]) {
if (!visited(rdd)) {
visited += rdd
val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
if (rddHasUncachedPartitions) {
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
}
case narrowDep: NarrowDependency[_] =>
waitingForVisit.push(narrowDep.rdd)
}
}
}
}
}
waitingForVisit.push(stage.rdd)
while (waitingForVisit.nonEmpty) {
visit(waitingForVisit.pop())
}
missing.toList
}
依存関係が広い場合、システムは自動的にShuffleMapStageを作成します
submitMissingTasks
private def submitMissingTasks(stage: Stage, jobId: Int) {
//计算分区
val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
...
//计算最佳位置
val taskIdToLocations: Map[Int, Seq[TaskLocation]] = try {
stage match {
case s: ShuffleMapStage =>
partitionsToCompute.map { id => (id, getPreferredLocs(stage.rdd,
id))}.toMap
case s: ResultStage =>
partitionsToCompute.map { id =>
val p = s.partitions(id)
(id, getPreferredLocs(stage.rdd, p))
}.toMap
}
} catch {
case NonFatal(e) =>
stage.makeNewStageAttempt(partitionsToCompute.size)
listenerBus.post(SparkListenerStageSubmitted(stage.latestInfo, properties))
abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}",
Some(e))
runningStages -= stage
return
}
//将分区映射TaskSet
val tasks: Seq[Task[_]] = try {
val serializedTaskMetrics =
closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
stage match {
case stage: ShuffleMapStage =>
stage.pendingPartitions.clear()
partitionsToCompute.map { id =>
val locs = taskIdToLocations(id)
val part = partitions(id)
stage.pendingPartitions += id
new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, properties, serializedTaskMetrics,
Option(jobId),
Option(sc.applicationId), sc.applicationAttemptId,
stage.rdd.isBarrier())
}
case stage: ResultStage =>
partitionsToCompute.map { id =>
val p: Int = stage.partitions(id)
val part = partitions(p)
val locs = taskIdToLocations(id)
new ResultTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, id, properties, serializedTaskMetrics,
Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
stage.rdd.isBarrier())
}
}
} catch {
case NonFatal(e) =>
abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}",
Some(e))
runningStages -= stage
return
}
//调⽤taskScheduler#submitTasks TaskSet
if (tasks.size > 0) {
logInfo(s"Submitting ${tasks.size} missing tasks from $stage (${stage.rdd})
(first 15 " +
s"tasks are for partitions ${tasks.take(15).map(_.partitionId)})")
taskScheduler.submitTasks(new TaskSet(
tasks.toArray, stage.id, stage.latestInfo.attemptNumber, jobId, properties))
}
...
}
总結関連键字:逆推、finalRDD、ResultStage、Shu!leMapStage、Shu!leMapTask、ResultTask、Shu!leDependency、NarrowDependency、DAGScheduler、TaskScheduler、SchedulerBackend、
t(
tasks.toArray、stage.id、stage.latestInfo.attemptNumber、jobId 、プロパティ))
}
…
}
总结关键字:逆推、finalRDD、ResultStage 、Shu!leMapStage、Shu!leMapTask、ResultTask、Shu!leDependency、NarrowDependency、DAGScheduler、TaskScheduler、SchedulerBackend、
DAGSchedulerEventProcessLoop