1.Shuffle Write 和Shuffle Read具体发生在哪里

2.哪里用到了Partitioner

3.何为mapSideCombine

4.何时进行排序

之前已经看过spark shuffle源码了,现在总结一下一些之前没有理解的小知识点,作为一个总结。

用户自定义的Partitioner存到了哪里?

假设用户在调用reduceByKey时,传递了一个自定义的Partitioner,那么,这个Partitioner会被保存到ShuffleRDD的ShuffleDependency中。在进行Shuffle Write时,会使用这个Partitioner来对finalRDD.iterator(partition)的计算结果shuffle到不同的Bucket中。

何为mapSideCombine

reduceByKey默认是开启了mapSideCombine的,在进行shuffle write时会进行本地聚合,在shuffle read时,也会合并一下。举一个例子更好:

shuffle write阶段:

partition0:[(hello,1),(hello,1)]

partition1:[(hello,1),(word,1),(word,1)]

mapSideCombine后:

partition0:[(hello,2)]

partition1:[(hello,1),(word,2)]

hash shuffle后:

[(hello,2),(hello,1)]

[(word,2)]

hash read阶段:

[(hello,3)]

[(word,2)]

何时排序

排序操作发生在shuffle read 阶段。在shuffle read 进行完mapSideCombine之后,就开始进行排序了。

reduceByKey做了什么?

假设我们对rdd1调用了reduceByKey,那么最终的RDD依赖关系如下:rdd1->ShuffleRDD。rdd1.reduceByKey中,会做如下非常重要的事情:创建ShuffleRDD,在创建ShuffleRDD的过程中最最最重要的就是会创建ShuffleDependency,这个ShuffleDependency中有Aggregator,Partitioner,Ordering,parentRDD,mapSideCombine等重要的信息。为什么说ShuffleDependency非常重要,因为他是沟通Shuffle Writer和Shuffle Reader的一个重要桥梁。

Shuffle Write

Shuffle Write 发生在ShuffleMapTask.runTask中。首先反序列出rdd1和那个ShuffleDependency:(rdd1,dep),然后调用rdd1.iterator(partition)获取计算结果,再对计算结果进行ShuffleWriter,代码如下:

  1. override def runTask(context: TaskContext): MapStatus = {
  2. // Deserialize the RDD using the broadcast variable.
  3. val deserializeStartTime = System.currentTimeMillis()
  4. val ser = SparkEnv.get.closureSerializer.newInstance()
  5. //统计反序列化rdd和shuffleDependency的时间
  6. val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
  7. ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
  8. _executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime
  9.  
  10. metrics = Some(context.taskMetrics)
  11. var writer: ShuffleWriter[Any, Any] = null
  12. try {
  13. val manager = SparkEnv.get.shuffleManager
  14. writer = manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context)
  15. writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])
  16. return writer.stop(success = true).get
  17. } catch {
  18. case e: Exception =>
  19. try {
  20. if (writer != null) {
  21. writer.stop(success = false)
  22. }
  23. } catch {
  24. case e: Exception =>
  25. log.debug("Could not stop writer", e)
  26. }
  27. throw e
  28. }
  29. }

我们以HashSuffleWriter为例,在其write(),他就会用到mapSideCombine和Partitioner。如下:

  1. /** Write a bunch of records to this task's output */
  2. override def write(records: Iterator[Product2[K, V]]): Unit = {
  3. val iter = if (dep.aggregator.isDefined) {
  4. if (dep.mapSideCombine) {
  5. dep.aggregator.get.combineValuesByKey(records, context)
  6. } else {
  7. records
  8. }
  9. } else {
  10. require(!dep.mapSideCombine, "Map-side combine without Aggregator specified!")
  11. records
  12. }
  13.  
  14. for (elem <- iter) {
  15. val bucketId = dep.partitioner.getPartition(elem._1)
  16. shuffle.writers(bucketId).write(elem._1, elem._2)
  17. }
  18. }

Shuffle Read

shuffle Read发生在ShuffleRDD的compute中:

  1. override def compute(split: Partition, context: TaskContext): Iterator[(K, C)] = {
  2. val dep = dependencies.head.asInstanceOf[ShuffleDependency[K, V, C]]
  3. SparkEnv.get.shuffleManager.getReader(dep.shuffleHandle, split.index, split.index + 1, context)
  4. .read()
  5. .asInstanceOf[Iterator[(K, C)]]
  6. }

下面是HashShuffleReader的read():

  1. /** Read the combined key-values for this reduce task */
  2. override def read(): Iterator[Product2[K, C]] = {
  3. val ser = Serializer.getSerializer(dep.serializer)
  4. val iter = BlockStoreShuffleFetcher.fetch(handle.shuffleId, startPartition, context, ser)
  5.  
  6. val aggregatedIter: Iterator[Product2[K, C]] = if (dep.aggregator.isDefined) {
  7. if (dep.mapSideCombine) {
  8. new InterruptibleIterator(context, dep.aggregator.get.combineCombinersByKey(iter, context))
  9. } else {
  10. new InterruptibleIterator(context, dep.aggregator.get.combineValuesByKey(iter, context))
  11. }
  12. } else {
  13. require(!dep.mapSideCombine, "Map-side combine without Aggregator specified!")
  14.  
  15. // Convert the Product2s to pairs since this is what downstream RDDs currently expect
  16. iter.asInstanceOf[Iterator[Product2[K, C]]].map(pair => (pair._1, pair._2))
  17. }
  18.  
  19. // Sort the output if there is a sort ordering defined.
  20. dep.keyOrdering match {
  21. case Some(keyOrd: Ordering[K]) =>
  22. // Create an ExternalSorter to sort the data. Note that if spark.shuffle.spill is disabled,
  23. // the ExternalSorter won't spill to disk.
  24. val sorter = new ExternalSorter[K, C, C](ordering = Some(keyOrd), serializer = Some(ser))
  25. sorter.insertAll(aggregatedIter)
  26. context.taskMetrics.incMemoryBytesSpilled(sorter.memoryBytesSpilled)
  27. context.taskMetrics.incDiskBytesSpilled(sorter.diskBytesSpilled)
  28. sorter.iterator
  29. case None =>
  30. aggregatedIter
  31. }
  32. }

spark shuffle 相关细节整理的更多相关文章

  1. Chrome浏览器相关细节整理

    一.上传文件卡死 可能时由于输入法的原因导致上传文件浏览器卡死.将输入法改为英文模式再操作上传文件就不会卡死了.

  2. Spark Shuffle的技术演进

      在Spark或Hadoop MapReduce的分布式计算框架中,数据被按照key分成一块一块的分区,打散分布在集群中各个节点的物理存储或内存空间中,每个计算任务一次处理一个分区,但map端和re ...

  3. Spark 性能相关参数配置详解-shuffle篇

    随着Spark的逐渐成熟完善, 越来越多的可配置参数被添加到Spark中来, 在Spark的官方文档http://spark.apache.org/docs/latest/configuration. ...

  4. Spark 性能相关參数配置具体解释-shuffle篇

    作者:刘旭晖 Raymond 转载请注明出处 Email:colorant at 163.com BLOG:http://blog.csdn.net/colorant/ 随着Spark的逐渐成熟完好, ...

  5. spark shuffle:分区原理及相关的疑问

    一.分区原理 1.为什么要分区?(这个借用别人的一段话来阐述.) 为了减少网络传输,需要增加cpu计算负载.数据分区,在分布式集群里,网络通信的代价很大,减少网络传输可以极大提升性能.mapreduc ...

  6. spark shuffle写操作之SortShuffleWriter

    提出问题 1. spark shuffle的预聚合操作是如何做的,其中底层的数据结构是什么?在数据写入到内存中有预聚合,在读溢出文件合并到最终的文件时是否也有预聚合操作? 2. shuffle数据的排 ...

  7. Spark面试相关

    Spark Core面试篇01 随着Spark技术在企业中应用越来越广泛,Spark成为大数据开发必须掌握的技能.前期分享了很多关于Spark的学习视频和文章,为了进一步巩固和掌握Spark,在原有s ...

  8. Spark Shuffle(一)ShuffleWrite:Executor如何将Shuffle的结果进行归并写到数据文件中去(转载)

    转载自:https://blog.csdn.net/raintungli/article/details/70807376 当Executor进行reduce运算的时候,生成运算结果的临时Shuffl ...

  9. Spark 性能相关参数配置详解-任务调度篇

    随着Spark的逐渐成熟完善, 越来越多的可配置参数被添加到Spark中来, 本文试图通过阐述这其中部分参数的工作原理和配置思路, 和大家一起探讨一下如何根据实际场合对Spark进行配置优化. 由于篇 ...

随机推荐

  1. Android6.0权限组申请

    void checkPermission() { final List<String> permissionsList = new ArrayList<>(); if (Bui ...

  2. 保存emoji到数据库

    /// <summary> /// <summary> /// 字符串转Unicode /// </summary> /// <param name=&quo ...

  3. Erlang C1500K长连接推送服务-性能

    Whatsapp已经使用Erlang在生产环境跑到96GB内存单机 3M长连接,参加:WhatsApp的Erlang世界.毕竟业务级别能达到Whatsapp那样极少,现在只有千万级,单机太多挂一台影响 ...

  4. Redis 3.0 Cluster集群配置

    Redis 3.0 Cluster集群配置 安装环境依赖 安装gcc:yum install gcc 安装zlib:yum install zib 安装ruby:yum install ruby 安装 ...

  5. Splinter学习——不仅仅是自动化测试哦

    前两天,想抢购一个小米MIX,结果,一开始抢就没有了.于是想,作为程序猿,总得有点特殊手段吧,比如说一个小脚本.最近在学习python,百度了一下,发现了Splinter这个强大的东东!用了不到两小时 ...

  6. Redis之清除所有缓存

    方法: /// <summary> /// 清除redis所有缓存 /// </summary> /// <param name="redisUrl" ...

  7. xml之dom4j解析

    * 使用dom4j解析xml 实例在java520里面 TextDom4j * dom4j,是一个组织,针对xml解析,提供解析器dom4j * dom4j不是javase的一部分,想要使用第一步需要 ...

  8. python关于分割与拼接的那些事

    1.split分割 基于re模块和正则表达式对象的方法split(),以后再做学习 基于字符串的split()方法 :字符串对象的split()方法也只能处理非常简单的情况,而且不支持多个分隔符,对分 ...

  9. 《Scalable IO in Java》笔记

    Scalable IO in Java http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf 基本上所有的网络处理程序都有以下基本的处理过程:Read reque ...

  10. JS实现常用排序算法—经典的轮子值得再造

    关于排序算法的博客何止千千万了,也不多一个轮子,那我就斗胆粗制滥造个轮子吧!下面的排序算法未作说明默认是从小到大排序. 1.快速排序2.归并排序3.冒泡排序4.选择排序(简单选择排序)5.插入排序(直 ...