Recherche sur le parallélisme SparkRDD et le code source de l'algorithme de partition

0 Préface

1 Parallélisme et partition RDD

1.1 Explication du concept

Par défaut, Spark peut diviser un travail en plusieurs tâches et les envoyer au nœud Executor pour un calcul parallèle. Le nombre de tâches qui peuvent être calculées en parallèle est appelé le degré de parallélisme. Ce nombre peut être spécifié lors de la construction du RDD. N'oubliez pas que le nombre de tâches exécutées en parallèle ici ne fait pas référence au nombre de tâches fractionnées, alors ne vous trompez pas.

1.2 Parallélisme des données et algorithme de partition lors de la lecture de la mémoire

1.2.1 Lecture de l'algorithme de parallélisme des données de la mémoire

Code source de makeRDD

  def makeRDD[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    parallelize(seq, numSlices)
  }

À partir du code source de makeRDD, nous pouvons voir que la couche inférieure des appels makeRDD est parallélisée (seq, numSlices), ce qui signifie que makeRDD est un package de parallelize. À partir du code source, vous pouvez savoir que makeRDD doit passer deux paramètres, l'un est la source de données (séquence) créée à partir de la mémoire, et l'autre est numSlices, qui nécessite le degré de parallélisme (le nombre de tranches) que vous passez La valeur par défaut est numSlices: Int = defaultParallelism (Le degré de parallélisme par défaut).

Le nombre de partitions du RDD par défaut est le degré de parallélisme, et la définition du degré de parallélisme consiste à définir le nombre de partitions. Cependant, le nombre de partitions n'est pas nécessairement le degré de parallélisme. Par exemple, le nombre de partitions et le degré de parallélisme ne sont pas égaux lorsque les ressources sont insuffisantes. Le degré de parallélisme peut être modifié via le deuxième paramètre de makeRDD Quel est le degré de parallélisme par défaut? Quelles sont les règles de base du partitionnement des données?

Cliquez pour afficher le code source:

(1) Cliquez pour afficher le code source à l'emplacement makeRDD

Le code après saisie est le suivant:

(2) Continuez à entrer et à afficher

Il s'agit d'une méthode abstraite, et une méthode abstraite doit avoir une classe d'implémentation: idée pour afficher la méthode de la classe d'implémentation, crtl + alt + b

(3) Les résultats de la recherche sont les suivants

 

(5) Enfin trouvé le code source comme suit

astuces: trouvez la méthode de la classe d'implémentation dans idea crtl + alt + b. Trouvez la méthode après ctrl + f

Le code source final final est le suivant:

  override def defaultParallelism(): Int =
    scheduler.conf.getInt("spark.default.parallelism", totalCores)

On peut voir à partir du code source que si les paramètres spécifiés ("spark.default.parallelism") ne peuvent pas être obtenus, la valeur par défaut totalCores sera utilisée, et totalCores est le nombre total de cœurs de machine disponibles dans l'environnement actuel. Qu'est-ce que cela signifie? Le nombre de cœurs est déterminé par les paramètres définis par le code suivant

val conf = new SparkConf().setAppName("word count").setMaster("local") 

La relation spécifique est illustrée dans la figure suivante:

Le paramètre configuré dans setMaster () est le nombre de cœurs disponibles dans l'environnement actuel. L'environnement actuel comprend l'environnement Windows et l'environnement Linux, etc.

  • Quand il est loacal, cela signifie qu'il n'y a qu'un seul noyau
  • Lorsqu'il est local [*], cela signifie tous les cœurs de la machine
  • Lorsqu'il est local [3], cela signifie utiliser 3 cœurs, qui peuvent être spécifiés par ce paramètre.

L'exemple de code spécifique est le suivant

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()
    }

}

Le résultat de l'exécution du code est indiqué dans la figure. Étant donné que l'environnement Windows local comporte huit cœurs, 8 fichiers sont générés.

 

1.2.2 Lire l'algorithme de partition de données de mémoire

L'algorithme de partition fait référence à la partition dans laquelle les données sont finalement allouées pour le calcul. Un fichier est généré à partir d'une partition

1 essai

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()
    }



}

Prenez le code RDD4 ci-dessus comme exemple, et les résultats sont les suivants:

À partir du code, nous pouvons voir qu'il y a 3 partitions, puis 3 fichiers seront générés

Ouvrez le fichier part-00000 et constatez qu'il n'y en a qu'un.
Ouvrez le fichier part-00001 et recherchez 2, 3.
Ouvrez le fichier part-00002 et recherchez 4 et 5.

Pourquoi en est-il ainsi?

2 Afficher le code source

 (1) Cliquez sur makeRDD pour afficher le code source

 (2) Cliquez sur paralléliser (seq, numSlices) pour voir plus loin

(3) Étape 3

(4) Étape 4

Le code de partition de données final est le suivant:

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
    }
  }
}

En analysant le code ci-dessus, on peut savoir que les données créées dans la mémoire iront éventuellement à la section case _ du code. La méthode appelée par ce code est:

Les paramètres transmis sont la longueur du tableau et le nombre de fragments

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

Le code est expliqué comme suit:

Par exemple:

 Remarque: jusqu'à ce que soit laissé fermé et ouvert à droite

Résumé: Lorsque les données de la mémoire peuvent être divisibles, elles sont globalement uniformément réparties. Si elles ne peuvent pas être divisibles, elles seront distribuées selon un certain algorithme.

1.3 Algorithme de partition de données lors de la lecture de fichiers

Lors de la lecture des données de fichier, les données sont découpées et partitionnées selon les règles de lecture de fichier Hadoop, et il existe certaines différences entre les règles de découpage et les règles de lecture des données. Le code source spécifique de Spark Core est le suivant

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));

  }

 

Test de partition

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()
    }
}

 

Je suppose que tu aimes

Origine blog.csdn.net/godlovedaniel/article/details/108155531
conseillé
Classement