spark shuffle流程入门

shuffle操作

Spark中的某些操作会触发一个称为shuffle的事件。shuffle是Spark重新分发数据的机制,以便在分区之间以不同的方式分组。这通常涉及到在执行器和计算机之间复制数据,从而使shuffle成为一项复杂而昂贵的操作。

背景

为了理解shuffle过程中会发生什么,我们可以考虑reduceByKey操作的例子。reduceByKey操作生成一个新的RDD,其中一个键的所有值都被组合到一个元组中,这个元组是对与该键相关联的所有值执行reduce函数的结果。问题是,不是一个键的所有值都必须驻留在同一个分区上,甚至是同一台机器上,但它们必须位于同一个位置才能计算结果。

在Spark中,数据通常不会跨分区分布到特定操作所需的位置。在计算过程中,单个任务将在单个分区上运行,因此,要组织单个reduceByKey reduce任务的所有数据,Spark需要执行all to all操作。它必须从所有分区中读取,以找到所有键的所有值,然后将跨分区的值合并在一起,以计算每个键的最终结果—这称为shuffle。

虽然新的无序数据的每个分区中的元素集是确定的,分区本身的顺序也是确定的,但是这些元素的顺序不是。如果您希望在shuffle后获得可预测的有序数据,则可以使用:

mapPartitions to sort each partition using, for example, .sorted
repartitionAndSortWithinPartitions to efficiently sort partitions
while simultaneously repartitioning sortBy to make a globally ordered
RDD

可能导致shuffle的操作包括重新分区操作(如重新分区和合并)、ByKey操作(如groupByKey和reduceByKey)以及连接操作(如cogroup和join)。

性能影响

Shuffle是一个昂贵的操作,因为它涉及到磁盘I/O、数据序列化和网络I/O。为了组织数据,Spark会生成一组任务——映射任务来组织数据,以及一组reduce任务来聚合数据。这个术语来自MapReduce,与Spark的map和reduce操作没有直接关系。

在内部,各个映射任务的结果都会保存在内存中,直到它们无法保存为止。然后,根据目标分区对这些文件进行排序,并将其写入单个文件。在reduce端,任务读取相关的排序块。

某些shuffle操作会消耗大量堆内存,因为它们在传输记录之前或之后使用内存中的数据结构来组织记录。具体来说,reduceByKey和aggregateByKey在map端创建这些结构,'ByKey操作在reduce端生成这些结构。当数据不适合内存时,Spark会将这些表溢出到磁盘,从而导致额外的磁盘I/O开销和增加的垃圾收集。

Shuffle还会在磁盘上生成大量中间文件。从Spark 1.3开始,这些文件将被保留,直到相应的RDD不再使用并被垃圾回收。这样做是为了在重新计算沿袭时不需要重新创建shuffle文件。如果应用程序保留对这些RDD的引用或GC不经常启动,则垃圾回收可能只在很长一段时间后发生。这意味着长时间运行的Spark作业可能会消耗大量磁盘空间。临时存储目录由spark.local.dir配置Spark上下文时的配置参数。

shuffle行为可以通过调整各种配置参数来调整。请参阅Spark配置指南.

shuffle关键

HashShuffle
1.每个shuffleMapTask会先创建指定分区数量的 buffer缓冲区,默认大小是32kb.
2 shuffleMapTask将相应的数据写到对应的buffer里,然后溢写成blockfile
3 所有的blockfile的位置信息封装到Mapstatus传递给DAGScheduler
4开始shuffle read阶段,相应的reduceTask会通过DAGScheduler的调度获取要读取文件的位置信息
5 reduce Task会维护一个buffer,用于缓存最多一次拉取的数据,默认48M
6最后形成shuffleRDD

优化后HashShuffle
加入shufflegroup
主要改进之处在于同一个executor里的所有的shuffleMapTask 为下游的每一个redcueTask只维护一个buffer和blockfile.减少了磁盘IO.

普通的SortShuffleManager,
是需要先排序的

  1. 每一个shuffleMapTask会根据指定的条件(字段,数量)将数据分成数量对应的份数据。每一份都对应一个buffer。
  2. 但是在进入buffer前,会先转成Map或Array进行排序,将属于同一个分区的数据写到buffer里
  3. buffer里的数据会溢写到磁盘上,形成文件(到此为止,和0.8版本以前的hashShufflerManager一样)
  4. 这个task产生的多个有序文件,会进行合并,形成一个具有分区的文件。为了下游的reduceTask来读取数据,
    因此有维护了一个索引文件,来记录此文件中的分区号,每个区数据的开始偏移量,以及长度。
    到此为止 shuffle write阶段结束
  5. 开始shuffle read阶段,首先获取索引文件信息。之后都和其他机制一样。

byPass机制的SortShuffleManager。
有些shuffle,不需要排序,如果排序,反而降低了性能。因此在普通的SortShuffleManager上设置了一个开关机制ByPass。 当对应的参数值小于200时,开始byPass机制,大于等于不开启。

注意: 如果连续调用的shuffle算子,分区字段都是一样的,产生的是窄依赖,因此不是stage的分界线
换句话说就是:
stage的分界线一定是一个shuffle算子,而shuffle算子不一定是一个stage的分界线。

案例

def test5(): Unit ={
    val conf: SparkConf = new SparkConf().setAppName("wordcount").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.textFile("file:///D:/tmp",3)
    println(rdd.getNumPartitions)//3
    val rdd2: RDD[(String, Int)] = rdd.flatMap((_.split(" "))).map(((x: String) => (x, 1)))
    val value: RDD[(String, Int)] = rdd2.reduceByKey(_ + _)
    println(value.getNumPartitions)//3
  }

与 RDD 不同,Spark SQL DataFrame API 在操作执行shuffle时会增加分区。
当 Spark 操作执行数据shuffle时,DataFrame 会自动将分区数添加到 200。默认shuffle分区编号来自 Spark SQL 配置,默认情况下设置为 200。spark.sql.shuffle.partitions

 def test6(): Unit ={
    val spark:SparkSession = SparkSession.builder()
      .master("local[1]")
      .appName("SparkByExamples.com")
      .getOrCreate()
    import spark.implicits._
    val simpleData = Seq(("James","Sales","NY",90000,34,10000),
      ("Michael","Sales","NY",86000,56,20000),
      ("Robert","Sales","CA",81000,30,23000),
      ("Maria","Finance","CA",90000,24,23000),
      ("Raman","Finance","CA",99000,40,24000),
      ("Scott","Finance","NY",83000,36,19000),
      ("Jen","Finance","NY",79000,53,15000),
      ("Jeff","Marketing","CA",80000,25,18000),
      ("Kumar","Marketing","NY",91000,50,21000)
    )
    val df: DataFrame = simpleData.toDF("employee_name","department","state","salary","age","bonus")
    val frame: DataFrame = df.groupBy("state").count()
    println(frame)//[state: string, count: bigint]
    println(frame.rdd.getNumPartitions)//200
  }

这个默认设置也是可以改的

spark.conf.set("spark.sql.shuffle.partitions",100)

你可能感兴趣的:(spark,spark,shuffle)