0まえがき
1RDDの並列処理とパーティション
1.1コンセプトの説明
デフォルトでは、Sparkはジョブを複数のタスクに分割し、並列計算のためにそれらをエグゼキューターノードに送信できます。並列で計算できるタスクの数は、並列度と呼ばれます。この番号は、RDDを作成するときに指定できます。ここで並行して実行されるタスクの数は、分割されたタスクの数を参照していないため、混乱しないでください。
1.2メモリを読み取るときのデータ並列性とパーティションアルゴリズム
1.2.1メモリデータ並列処理アルゴリズムの読み取り
makeRDDのソースコード
def makeRDD[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T] = withScope {
parallelize(seq, numSlices)
}
makeRDDのソースコードから、makeRDDの最下層がparallelize(seq、numSlices)を呼び出すことがわかります。これは、makeRDDが並列化のパッケージであることを意味します。ソースコードから、makeRDDが2つのパラメーターを渡す必要があることがわかります。1つはメモリから作成されたデータソース(シーケンス)であり、もう1つはnumSlicesであり、渡す並列度(スライス数)が必要です。 。デフォルトはnumSlicesです。Int= defaultParallelism(デフォルトの並列度)。
デフォルトのRDDのパーティション数は並列度であり、並列度の設定はパーティション数の設定です。ただし、パーティションの数は必ずしも並列度とは限りません。たとえば、リソースが不足している場合、パーティションの数と並列度は等しくありません。並列度は、makeRDDの2番目のパラメーターを使用して変更できます。デフォルトの並列度はいくつですか。データ分割のコアルールは何ですか?
クリックしてソースコードを表示します。
(1)makeRDDの場所にあるソースコードをクリックして表示します
入力後のコードは次のとおりです。
(2)入力と表示を続行します
これは抽象メソッドであることがわかり、抽象メソッドには実装クラスが必要です。実装クラスのメソッドを表示するためのアイデア、crtl + alt + b
(3)検索結果は以下のとおりです。
(5)最終的に次のようにソースコードを見つけました
ヒント:アイデアcrtl + alt + bで実装クラスのメソッドを見つけます。ctrl+ fの後にメソッドを見つけます
最終的なコアソースコードは次のとおりです。
override def defaultParallelism(): Int =
scheduler.conf.getInt("spark.default.parallelism", totalCores)
ソースコードから、指定されたパラメーター( "spark.default.parallelism")を取得できない場合、デフォルト値のtotalCoresが使用され、totalCoresは現在の環境で使用可能なマシンコアの総数であることがわかります。どういう意味ですか?コアの数は、次のコードで設定されたパラメーターによって決まります。
val conf = new SparkConf().setAppName("word count").setMaster("local")
具体的な関係を次の図に示します。
setMaster()で構成されたパラメーターは、現在の環境で使用可能なコアの数です。現在の環境には、Windows環境やLinux環境などがあります。
- それがloacalである場合、それは1つのコアしかないことを意味します
- ローカル[*]の場合、マシン内のすべてのコアを意味します
- local [3]の場合、このパラメーターで指定できる3つのコアを使用することを意味します。
具体的なサンプルコードは次のとおりです
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object testMakeRDD {
def main(args: Array[String]): Unit = {
//TODO spark从内存中创建RDD
//默认RDD分区的数量就是并行度,设定并行度就是设定分区数量,但分区数量不一定就是并行度
//资源不够的情况下分区数与并行度是不等的
val conf = new SparkConf().setAppName("word count").setMaster("local[*]") //单核数
//创建spark上下文
val sc = new SparkContext(conf)
//1.makeRDD的第一个参数:数据源
//2.makeRDD的第二个参数:并行度(分区数量)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//不传参数的话默认分区数是多少?
// println(rdd.collect().mkString(","))
// 将RDD的处理数据保存到文件中
rdd.saveAsTextFile("output")
sc.stop()
}
}
コードの実行結果を図に示します。ローカルWindows環境には8つのコアがあるため、8つのファイルが生成されます。
1.2.2読み取りメモリデータパーティションアルゴリズム
パーティションアルゴリズムは、データが最終的に計算のために割り当てられるパーティションを指します。1つのパーティションから1つのファイルが生成されます
1回のテスト
object testFenQu {
def main(args: Array[String]): Unit = {
//TODO spark从内存中创建RDD
val conf = new SparkConf().setAppName("word count").setMaster("local[*]")
//创建spark上下文
val sc = new SparkContext(conf)
// val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
// rdd1.saveAsTextFile("output")
// val rdd2: RDD[Int] = sc.makeRDD(List(1,2,3,4),4)
// rdd2.saveAsTextFile("output")
// val rdd3: RDD[Int] = sc.makeRDD(List(1,2,3,4),3)
// rdd3.saveAsTextFile("output")
val rdd4: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),3)
rdd4.saveAsTextFile("output")
sc.stop()
}
}
上記のコードRDD4を例にとると、結果は次のようになります。
コードから、3つのパーティションがあることがわかります。その後、3つのファイルが生成されます。
ファイルpart-00000を開き、1つしかないことを確認します。
ファイルpart-00001を開き、2、3を見つけます。
ファイルpart-00002を開き、4と5を見つけます。
なぜそうなのですか?
2ソースコードを表示する
(1)makeRDDをクリックして、ソースコードを表示します
(2)parallelize(seq、numSlices)をクリックしてさらに表示します
(3)ステップ3
(4)ステップ4
最終的なデータパーティションコードは次のとおりです。
private object ParallelCollectionRDD {
/**
* Slice a collection into numSlices sub-collections. One extra thing we do here is to treat Range
* collections specially, encoding the slices as other Ranges to minimize memory cost. This makes
* it efficient to run Spark over RDDs representing large sets of numbers. And if the collection
* is an inclusive Range, we use inclusive range for the last slice.
*/
def slice[T: ClassTag](seq: Seq[T], numSlices: Int): Seq[Seq[T]] = {
if (numSlices < 1) {
throw new IllegalArgumentException("Positive number of partitions required")
}
// Sequences need to be sliced at the same set of index positions for operations
// like RDD.zip() to behave as expected
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end)
}
}
seq match {
case r: Range =>
positions(r.length, numSlices).zipWithIndex.map { case ((start, end), index) =>
// If the range is inclusive, use inclusive range for the last slice
if (r.isInclusive && index == numSlices - 1) {
new Range.Inclusive(r.start + start * r.step, r.end, r.step)
}
else {
new Range(r.start + start * r.step, r.start + end * r.step, r.step)
}
}.toSeq.asInstanceOf[Seq[Seq[T]]]
case nr: NumericRange[_] =>
// For ranges of Long, Double, BigInteger, etc
val slices = new ArrayBuffer[Seq[T]](numSlices)
var r = nr
for ((start, end) <- positions(nr.length, numSlices)) {
val sliceSize = end - start
slices += r.take(sliceSize).asInstanceOf[Seq[T]]
r = r.drop(sliceSize)
}
slices
case _ =>
val array = seq.toArray // To prevent O(n^2) operations for List etc
positions(array.length, numSlices).map { case (start, end) =>
array.slice(start, end).toSeq
}.toSeq
}
}
}
上記のコードを分析することにより、メモリに作成されたデータが最終的にコードのcase_セクションに移動することがわかります。このコードによって呼び出されるメソッドは次のとおりです。
渡されるパラメーターは、配列の長さとフラグメントの数です。
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end) //返回数据的偏移量左闭右开
}
}
-------------------------------------
case _ =>
val array = seq.toArray // To prevent O(n^2) operations for List etc
positions(array.length, numSlices).map { case (start, end) =>
array.slice(start, end).toSeq
}.toSeq
コードは次のように説明されています。
例えば:
注:左に閉じて右に開くまで
概要:メモリ内のデータが分割可能である場合、基本的に均等に分散されます。分割できない場合、特定のアルゴリズムに従って分散されます。
1.3ファイルを読み取るときのデータ分割アルゴリズム
ファイルデータを読み取る場合、データはHadoopファイル読み取りのルールに従ってスライスおよびパーティション化され、スライスルールとデータ読み取りのルールにはいくつかの違いがあります。具体的なSparkコアソースコードは次のとおりです。
public InputSplit[] getSplits(JobConf job, int numSplits)
throws IOException {
long totalSize = 0; // compute total size
for (FileStatus file: files) { // check we have valid files
if (file.isDirectory()) {
throw new IOException("Not a file: "+ file.getPath());
}
totalSize += file.getLen();
}
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
...
for (FileStatus file: files) {
...
if (isSplitable(fs, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
...
}
protected long computeSplitSize(long goalSize, long minSize,
long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
パーティションテスト
object partition03_file {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkCoreTest1")
val sc: SparkContext = new SparkContext(conf)
//1)默认分区的数量:默认取值为当前核数和2的最小值
//val rdd: RDD[String] = sc.textFile("input")
//2)输入数据1-4,每行一个数字;输出:0=>{1、2} 1=>{3} 2=>{4} 3=>{空}
//val rdd: RDD[String] = sc.textFile("input/3.txt",3)
//3)输入数据1-4,一共一行;输出:0=>{1234} 1=>{空} 2=>{空} 3=>{空}
val rdd: RDD[String] = sc.textFile("input/4.txt",3)
rdd.saveAsTextFile("output")
sc.stop()
}
}