Apache_Spark源码走读系列篇二
学会使用ApacheSpark进行大数据分析和处理的基本操作

学会使用ApacheSpark进行大数据分析和处理的基本操作Apache Spark是一个快速、通用、可扩展的大数据处理引擎,被广泛应用于大数据分析和处理中。
学会使用Apache Spark进行大数据分析和处理的基本操作,对于数据科学家和大数据工程师来说至关重要。
本文将介绍Apache Spark的基本概念和操作,包括数据加载、转换、过滤、聚合以及输出等,以帮助读者快速上手使用Apache Spark进行大数据分析和处理。
第一章:Apache Spark简介与安装Apache Spark是一款开源的大数据处理框架,提供了高效的分布式计算能力,可以处理大规模的数据集。
在使用Apache Spark 之前,我们需要先安装Spark并配置好相应的环境。
具体的安装过程可以在Apache Spark官方网站上找到,并根据操作系统类型和版本进行安装、设置和配置。
第二章:数据加载与存储在使用Apache Spark进行大数据分析和处理之前,我们需要先将数据加载到Spark中。
Spark支持多种数据源和格式,如文本文件、CSV文件、JSON文件、数据库等。
可以使用Spark的API或工具(如spark-submit或spark-shell)来加载和读取数据。
除了加载数据,我们还可以将结果保存到各种外部存储介质中,如HDFS、S3或关系型数据库等。
第三章:数据转换与过滤在数据分析和处理过程中,常常需要对数据进行转换和过滤以满足需求。
Apache Spark提供了丰富的转换和过滤操作,如映射、过滤、排序、去重等。
通过这些操作,我们可以对数据集进行加工和处理,以便于后续的分析和挖掘。
第四章:数据聚合与计算数据聚合是大数据处理中常见的操作之一,Apache Spark提供了多种聚合和计算函数,如求和、平均值、最大值、最小值、统计等。
通过这些函数,我们可以对数据集进行统计和计算,以获取更有价值的信息。
此外,Spark还支持自定义聚合函数和窗口函数,可以满足更加复杂的需求。
Spark源码分析之Driver和Excutor是怎么跑起来的?(2.2.0版本)

Spark源码分析之Driver和Excutor是怎么跑起来的?(2.2.0版本)今天抽空回顾了⼀下Spark相关的源码,本来想要了解⼀下Block的管理机制,但是看着看着就回到了SparkContext的创建与使⽤。
正好之前没有正式的整理过这部分的内容,这次就顺带着回顾⼀下。
更多内容参考:Spark作为⽬前最流⾏的⼤数据计算框架,已经发展了⼏个年头了。
版本也从我刚接触的1.6升级到了2.2.1。
由于⽬前⼯作使⽤的是2.2.0,所以这次的分析也就从2.2.0版本⼊⼿了。
涉及的内容主要有:Standalone模式中的Master与Workerclient、driver、excutor的关系下⾯就按照顺序依次介绍⼀下。
在最开始编程的时候,很少会涉及分布式,因为数据量也不⼤。
后来随着硬件的发展cpu的瓶颈,开始流⾏多线程编程,基于多线程来加快处理速度;再后来,衍⽣出了⽹格计算、CPU与GPU的异构并⾏计算以及当时流⾏的mapreduce分布式计算。
但是mapreduce由于存储以及计算流程的限制,spark开始流⾏起来。
Spark凭借内存计算、强⼤的DAG回溯能⼒,快速的占领并⾏计算的风⼝。
那么并⾏计算肯定是需要分布式集群的,常见的集群管理⽅式,有Master-Slave模式、P2P模式等等。
⽐如Mysql的主从复制,就是Master-Slave模式;Elasticsearch的分⽚管理就是P2P模式。
在Spark中有不同的部署⽅式,但是计算的模式都是Master-Slave模式,只不过Slave换了名字叫做worker⽽已。
集群的部署模式如下所⽰:流程就是⽤户以client的⾝份向master提交任务,master去worker上⾯创建执⾏任务的载体(driver和excutor)。
Master和Worker是服务器的部署⾓⾊,程序从执⾏上,则分成了client、driver、excutor三种⾓⾊。
Spark集群启动之Master、Worker启动流程源码分析

Spark集群启动之Master、Worker启动流程源码分析Spark集群启动Master可以使用脚本启动:start-master,shell脚本细节自行查看。
最终启动命令为:Java -cp /home/daxin/bigdata/spark/conf/:/home/daxin/bigdata/spark/jars/*:/home/daxin/bigdata/Hadoop/et c/hadoop/ -Xmx1g -XX:MaxPermSize=256m org.apache.spark.deploy.master.Master --host node --port 7077 --webui-port 8080最终转换为java命令启动Master的过程,所以我们就需要查看一下Master的main方法代码如下:[java] view plain copy 在CODE上查看代码片派生到我的代码片val systemName = "sparkMaster"private val actorName = "Master"/*** spark-class脚本调用,启动master** @param argStrings*/def main(argStrings: Array[String]) {SignalLogger.register(log)//参数配置准备val conf = new SparkConfval args = new MasterArguments(argStrings, conf)//创建actorSystem// (actorSystem, boundPort, portsResponse.webUIPort, portsResponse.restPort)val (actorSystem, _, _, _) = startSystemAndActor(args.host, args.port, args.webUiPort, conf) actorSystem.awaitTermination()}通过代码可以可以知道调用startSystemAndActor方法完成ActorSystem和Actor的创建。
代码走读——精选推荐

代码⾛读 ⼀、代码⾛读的内容 代码⾛读在软件开发过程⼗分的重要,能及时的发现并解决问题,那么代码⾛读有哪些内容呢? 1、检查是否符合编程规范:开发⼈员的编码风格是否规范,是否有注释,编写的代码能否让其他的编程⼈员阅读及维护,编程中的变量命名是否合适,是否缺少空格等。
2、寻找编译器中的设计陷阱:编程和设计过程中常见的和可防⽌的问题,能顺利通过编译,没有任何警告和错误信息,⽽且计算机能严格按照代码执⾏。
3、快速理解源代码,找出流程设计中的问题:将源代码编译成可执⾏程序,也可以阅读代码来了解程序的功能及其⼯作⽅式,还可以修改源代码来改变程序的功能从⽽找出逻辑上存在的问题,要求检查者要读懂代码,并且熟悉业务。
4、架构:包含类之间的关系,某个函数的实现。
如果不考虑后期维护可以忽略这层,或是有强⼤的架构设计师。
其实这类问题⽐逻辑更容易发现,例如某个类功能太多或函数if\switch太多等。
5、对原有代码的重构:重构就是在不破坏可观察功能的前提下,借由搬移、提炼、打散、凝聚……,改善事务的体质、强化当前的可读性、为将来的扩充性和维护性做准备、乃⾄于在过程中找出潜在的错误。
⼆、代码⾛读的⽅法 1、反复推敲 同⼀个逻辑可以有很多⽅式描述,但⽤哪个更好更合适可以在⾛读时细细体会,推敲的标准是1.⾼内聚低耦合 2.接⼝优先 3.好看好理解4.⾼效,运⾏速度快。
2、过段时间复读 ⽂章放段时间再拿出来看能发现很多问题,代码⼀样,⼀段时间后⼈的思维惯性没那么强了,改代码的抵触⼼理也会少很多,更容易发现问题。
除了开发⼈员需要进⾏代码⾛读外,⽩盒测试⼈员在进⾏测试时也需要简单的进⾏代码⾛读,从测试⾓度找出编码中存在的问题,及时的让开发⼈员改正,从⽽保证代码的⾼质量。
Spark内核源码解析十二:shuffle原理解析

Spark内核源码解析⼗⼆:shuffle原理解析第⼀个特点,在Spark早期版本中,那个bucket缓存是⾮常⾮常重要的,因为需要将⼀个ShuffleMapTask所有的数据都写⼊内存缓存之后,才会刷新到磁盘。
但是这就有⼀个问题,如果map side数据过多,那么很容易造成内存溢出。
所以spark在新版本中,优化了,默认那个内存缓存是100kb,然后呢,写⼊⼀点数据达到了刷新到磁盘的阈值之后,就会将数据⼀点⼀点地刷新到磁盘。
这种操作的优点,是不容易发⽣内存溢出。
缺点在于,如果内存缓存过⼩的话,那么可能发⽣过多的磁盘写io操作。
所以,这⾥的内存缓存⼤⼩,是可以根据实际的业务情况进⾏优化的。
第⼆个特点,与MapReduce完全不⼀样的是,MapReduce它必须将所有的数据都写⼊本地磁盘⽂件以后,才能启动reduce操作,来拉取数据。
为什么?因为mapreduce要实现默认的根据key的排序!所以要排序,肯定得写完所有数据,才能排序,然后reduce来拉取。
但是Spark不需要,spark默认情况下,是不会对数据进⾏排序的。
因此ShuffleMapTask每写⼊⼀点数据,ResultTask就可以拉取⼀点数据,然后在本地执⾏我们定义的聚合函数和算⼦,进⾏计算。
spark这种机制的好处在于,速度⽐mapreduce快多了。
但是也有⼀个问题,mapreduce提供的reduce,是可以处理每个key对应的value上的,很⽅便。
但是spark中,由于这种实时拉取的机制,因此提供不了,直接处理key对应的values的算⼦,只能通过groupByKey,先shuffle,有⼀个MapPartitionsRDD,然后⽤map算⼦,来处理每个key对应的values。
就没有mapreduce的计算模型那么⽅便。
shuffle原理图如下优化后也就是加⼊consolidation机制后的原理图如下,主要解决产⽣的⽂件太多在executor中执⾏任务时,主要是task的实现类来执⾏任务,其中shuffleMapTask,将针对rdd执⾏算⼦后的结果写⼊磁盘// 有mapstatus返回值,override def runTask(context: TaskContext): MapStatus = {// Deserialize the RDD using the broadcast variable.// 对要处理的rdd相关数据,做⼀些反序列化的,这个rdd是怎么拿到的,多个task运⾏在executor⾥⾯,并⾏运⾏或者并发运⾏// 可能不在⼀个地⽅,但是⼀个stage的task,要处理的rdd都是⼀样的,通过broadcast variable拿到val ser = SparkEnv.get.closureSerializer.newInstance()val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)metrics = Some(context.taskMetrics)var writer: ShuffleWriter[Any, Any] = nulltry {// 获取shuffleManagerval manager = SparkEnv.get.shuffleManagerwriter = manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context)// 调⽤rdd的iterator⽅法,并且传⼊当前task要处理哪个partition,核⼼逻辑就在rdd的iterator// ⽅法中在这⾥实现了针对某个partition执⾏算⼦和函数,针对rdd的partition进⾏处理,有返回数据通过shuffleWriter经过// HashPartition写⼊⾃⼰的分区,mapstatus封装了shufflemaptask计算后的数据,存储在那⾥,就是blockmanager信息// blockmanager就是spark底层内存、数据、磁盘管理组件writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])return writer.stop(success = true).get} catch {case e: Exception =>try {if (writer != null) {writer.stop(success = false)}} catch {case e: Exception =>log.debug("Could not stop writer", e)}throw e}}shuffle写的⼊⼝再HashShuffleWriter⾥⾯/** Write a bunch of records to this task's output* 将每个shuffleMapTask计算出来的新的RDD的partition数据,写⼊磁盘* */override def write(records: Iterator[_ <: Product2[K, V]]): Unit = {// ⾸先判断,是否需要在map端本地进⾏聚合,这⾥的话,如果是reduceBykey这种操作,它的dep.aggregator.isDefined就是true// 包括dep.mapSideCombine也是true// 那么就就进⾏map端的本地聚合val iter = if (dep.aggregator.isDefined) {if (dep.mapSideCombine) {// 执⾏本地聚合,如(hello,1)(hello,1)就成了(hello,2)bineValuesByKey(records, context)} else {records}//} else {require(!dep.mapSideCombine, "Map-side combine without Aggregator specified!")records}// 如果要本地聚合,那么先本地聚合,然后遍历数据,对每个数据掉⽤partitioner// ,默认是hashPartitioner,⽣成bucketId,也就是决定每⼀份数据要写⼊那个bucket。
SparkSQL源码解析(二)Antlr4解析Sql并生成树

SparkSQL源码解析(⼆)Antlr4解析Sql并⽣成树Spark SQL原理解析前⾔:这⼀次要开始真正介绍Spark解析SQL的流程,⾸先是从Sql Parse阶段开始,简单点说,这个阶段就是使⽤Antlr4,将⼀条Sql语句解析成语法树。
可能有童鞋没接触过antlr4这个内容,推荐看看《antlr4权威指南》前四章,看完起码知道antlr4能⼲嘛。
我这⾥就不多介绍了。
这篇⾸先先介绍调⽤spark.sql()时候的流程,再看看antlr4在这个其中的主要功能,最后再将探究Logical Plan究竟是什么东西。
初始流程当你调⽤spark.sql的时候,会调⽤下⾯的⽅法:def sql(sqlText: String): DataFrame = {Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))}parse sql阶段主要是parsePlan(sqlText)这⼀部分。
⽽这⾥⼜会辗转去org.apache.spark.sql.catalyst.parser.AbstractSqlParser调⽤parse⽅法。
这⾥贴下关键代码。
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {logDebug(s"Parsing command: $command")val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))lexer.removeErrorListeners()lexer.addErrorListener(ParseErrorListener)lexer.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforcedval tokenStream = new CommonTokenStream(lexer)val parser = new SqlBaseParser(tokenStream)parser.addParseListener(PostProcessor)parser.removeErrorListeners()parser.addErrorListener(ParseErrorListener)parser.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforcedtry {try {// first, try parsing with potentially faster SLL modeparser.getInterpreter.setPredictionMode(PredictionMode.SLL)toResult(parser)}catch {case e: ParseCancellationException =>// if we fail, parse with LL modetokenStream.seek(0) // rewind input streamparser.reset()// Try Again.parser.getInterpreter.setPredictionMode(PredictionMode.LL)toResult(parser)}}catch {case e: ParseException if mand.isDefined =>throw ecase e: ParseException =>throw e.withCommand(command)case e: AnalysisException =>val position = Origin(e.line, e.startPosition)throw new ParseException(Option(command), e.message, position, position)}}可以发现,这⾥⾯的处理逻辑,⽆论是SqlBaseLexer还是SqlBaseParser都是Antlr4的东西,包括最后的toResult(parser)也是调⽤访问者模式的类去遍历语法树来⽣成Logical Plan。
ApacheSpark框架详细介绍

ApacheSpark框架详细介绍Apache Spark框架详细介绍Apache Spark是一个快速、通用的大数据处理框架,由加州大学伯克利分校的AMPLab开发。
它提供了一种高级的分布式编程模型,可以处理包含数十亿行数据的大规模数据集。
本文将详细介绍Apache Spark框架的核心组件、特点和使用场景。
一、Spark的核心组件Apache Spark框架由以下核心组件构成:1. Spark Core:Spark的基础功能模块,提供了任务调度、内存管理、错误恢复等核心功能。
它还包含了RDD(弹性分布式数据集)的实现,RDD是Spark的主要数据结构,它是一个可分区、可并行计算的数据集合。
2. Spark SQL:提供了对结构化数据的查询和分析功能,支持SQL查询、数据流处理和机器学习等操作。
Spark SQL可以将结构化数据映射成DataFrame,DataFrame是一种类似于关系型数据库的数据结构。
3. Spark Streaming:用于实时流式数据的处理和分析,可以处理实时生成的数据流,并将其划分成小批次进行计算。
Spark Streaming支持各种数据源,如Kafka、Flume等。
4. MLlib:为Spark提供了机器学习功能,包括常见的分类、回归、聚类和推荐算法等。
MLlib提供了丰富的机器学习工具和算法库,能够处理大规模的机器学习任务。
5. GraphX:用于图计算的组件,支持图的创建、操作和分析。
GraphX提供了图的基本算法和图计算的API,可以用于社交网络分析、推荐系统等领域。
二、Spark的特点Apache Spark相比于其他大数据处理框架,具有以下几个显著特点:1. 快速性能:Spark采用内存计算,能够将数据存储在内存中,从而加速数据处理的速度。
它还支持并行计算,能够在多个节点上同时执行任务,提高了处理效率。
2. 容错性:Spark具有良好的容错能力,能够在节点故障时自动恢复任务,保证计算的可靠性。
Spark源码系列(十)spark源码解析大全

Spark源码系列(⼗)spark源码解析⼤全第1章Spark 整体概述1.1 整体概念 A p a c h e Sp a r k是⼀个开源的通⽤集群计算系统,它提供了H ig h-l e v e l编程A P I,⽀持Sc a l a、J a v a和P y t h o n三种编程语⾔。
Sp a r k内核使⽤Sc a l a 语⾔编写,通过基于Sc a l a的函数式编程特性,在不同的计算层⾯进⾏抽象,代码设计⾮常优秀。
1.2 RDD 抽象1.3 计算抽象在St a n d a l o n e模式下,默认使⽤的是FI FO这种简单的调度策略,在进⾏调度的过程中,⼤概流程如下图所⽰:1.4 集群模式 Sp a r k集群在设计的时候,并没有在资源管理的设计上对外封闭,⽽是充分考虑了未来对接⼀些更强⼤的资源管理系统,如Y A R N、M e s o s等,所以Sp a r k架构设计将资源管理单独抽象出⼀层,通过这种抽象能够构建⼀种适合企业当前技术栈的插件式资源管理模块,从⽽为不同的计算场景提供不同的资源分配与调度策略。
Sp a r k集群模式架构,如下图所⽰:上图中,Sp a r k集群C l u s t e r M a n a g e r⽬前⽀持如下三种模式:1)St a n d a l o n e模式 ·St a n d a l o n e模式是Sp a r k内部默认实现的⼀种集群管理模式,这种模式是通过集群中的M a s t e r来统⼀管理资源,⽽与M a s t e r进⾏资源请求协商的是D r iv e r内部的St a n d a l o n e Sc h e d u l e r B a c k e n d(实际上是其内部的St a n d a l o n e A p p C l ie n t真正与M a s t e r通信),后⾯会详细说明。
2)Y A R N模式 ·Y A R N模式下,可以将资源的管理统⼀交给Y A R N集群的R e s o u r c e M a n a g e r去管理,选择这种模式,可以更⼤限度的适应企业内部已有的技术栈,如果企业内部已经在使⽤H a d o o p技术构建⼤数据处理平台。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
超人学院—Apache Spark源码走读之Task运行期之函数调用关系分析欢迎转载,转载请注明出处,超人学院。
概要本篇主要阐述在TaskRunner中执行的task其业务逻辑是如何被调用到的,另外试图讲清楚运行着的task其输入的数据从哪获取,处理的结果返回到哪里,如何返回。
准备1.spark已经安装完毕2.spark运行在local mode或local-cluster modelocal-cluster modelocal-cluster模式也称为伪分布式,可以使用如下指令运行MASTER=local[1,2,1024] bin/spark-shell[1,2,1024]分别表示,executor number, core number和内存大小,其中内存大小不应小于默认的512MDriver Programme的初始化过程分析初始化过程的涉及的主要源文件1.SparkContext.scala 整个初始化过程的入口2.SparkEnv.scala 创建BlockManager,MapOutputTrackerMaster, ConnectionManager, CacheManager3.DAGScheduler.scala 任务提交的入口,即将Job划分成各个stage的关键4.TaskSchedulerImpl.scala 决定每个stage可以运行几个task,每个task分别在哪个executor上运行5.SchedulerBackend1.最简单的单机运行模式的话,看LocalBackend.scala2.如果是集群模式,看源文件SparkDeploySchedulerBackend初始化过程步骤详解步骤1:根据初始化入参生成SparkConf,再根据SparkConf来创建SparkEnv, SparkEnv中主要包含以下关键性组件 1. BlockManager 2. MapOutputTracker 3. ShuffleFetcher 4. ConnectionManagerprivate[spark] val env = SparkEnv.create(conf,"",conf.get("spark.driver.host"),conf.get("spark.driver.port").toInt,isDriver = true,isLocal = isLocal)SparkEnv.set(env)步骤2:创建TaskScheduler,根据Spark的运行模式来选择相应的SchedulerBackend,同时启动taskscheduler,这一步至为关键private[spark] var taskScheduler =SparkContext.createTaskScheduler(this, master, appName)taskScheduler.start()TaskScheduler.start目的是启动相应的SchedulerBackend,并启动定时器进行检测overridedef start() {backend.start()if (!isLocal && conf.getBoolean("spark.speculation", false)) {logInfo("Starting speculative execution thread") import sc.env.actorSystem.dispatchersc.env.actorSystem.scheduler.schedule(SPECULATION_INTERVAL milliseconds,SPECULATION_INTERVAL milliseconds) {checkSpeculatableTasks()}}}步骤3:以上一步中创建的TaskScheduler实例为入参创建DAGScheduler并启动运行@volatileprivate[spark] var dagScheduler = new DAGScheduler(taskScheduler)dagScheduler.start()步骤4:启动WEB UIui.start()RDD的转换过程还是以最简单的wordcount为例说明rdd的转换过程sc.textFile("README.md").flatMap(line=>line.split(" ")).map(word => (word, 1)).reduceByKey(_ + _)上述一行简短的代码其实发生了很复杂的RDD转换,下面仔细解释每一步的转换过程和转换结果步骤1:val rawFile = sc.textFile("README.md")textFile先是生成hadoopRDD,然后再通过map操作生成MappedRDD,如果在spark-shell中执行上述语句,得到的结果可以证明所做的分析scala> sc.textFile("README.md")14/04/2313:11:48 WARN SizeEstimator: Failed to check whether UseCompressedOops is set; assuming yes14/04/2313:11:48 INFO MemoryStore: ensureFreeSpace(119741)called with curMem=0, maxMem=31138775014/04/2313:11:48 INFO MemoryStore: Block broadcast_0 stored asvalues to memory (estimated size 116.9 KB, free 296.8 MB)14/04/2313:11:48 DEBUG BlockManager: Put block broadcast_0locally took 277 ms14/04/2313:11:48 DEBUG BlockManager: Put for block broadcast_0without replication took 281 msres0: org.apache.spark.rdd.RDD[String] = MappedRDD[1] attextFile at :13步骤2: val splittedText = rawFile.flatMap(line =>line.split(" "))flatMap将原来的MappedRDD转换成为FlatMappedRDDdef flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]= new FlatMappedRDD(this, sc.clean(f))步骤3:val wordCount = splittedText.map(word => (word, 1))利用word生成相应的键值对,上一步的FlatMappedRDD被转换成为MappedRDD步骤4:val reduceJob = wordCount.reduceByKey(_ + _),这一步最复杂步骤2,3中使用到的operation全部定义在RDD.scala中,而这里使用到的reduceByKey却在RDD.scala中见不到踪迹。
reduceByKey 的定义出现在源文件PairRDDFunctions.scala细心的你一定会问reduceByKey不是MappedRDD的属性和方法啊,怎么能被MappedRDD调用呢?其实这背后发生了一个隐式的转换,该转换将MappedRDD转换成为PairRDDFunctionsimplicit def rddToPairRDDFunctions[K: ClassTag, V: ClassTag](rdd: RDD[(K, V)]) =new PairRDDFunctions(rdd)这种隐式的转换是scala的一个语法特征,如果想知道的更多,请用关键字"scala implicit method"进行查询,会有不少的文章对此进行详尽的介绍。
接下来再看一看reduceByKey的定义def reduceByKey(func: (V, V) => V): RDD[(K, V)] = {reduceByKey(defaultPartitioner(self), func)}def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = {combineByKey[V]((v: V) => v, func, func, partitioner)}def combineByKey[C](createCombiner: V => C,mergeValue: (C, V) => C,mergeCombiners: (C, C) => C,partitioner: Partitioner,mapSideCombine: Boolean = true,serializerClass: String = null): RDD[(K, C)] = {if (getKeyClass().isArray) {if (mapSideCombine) {thrownew SparkException("Cannot use map-side combining with array keys.")}if (partitioner.isInstanceOf[HashPartitioner]) {thrownew SparkException("Default partitioner cannot partition array keys.")}}val aggregator = new Aggregator[K, V, C](createCombiner, mergeValue, mergeCombiners)if (self.partitioner == Some(partitioner)) {self.mapPartitionsWithContext((context, iter) => { new InterruptibleIterator(context,bineValuesByKey(iter, context))}, preservesPartitioning = true)} elseif (mapSideCombine) {val combined = self.mapPartitionsWithContext((context, iter) => {bineValuesByKey(iter, context)}, preservesPartitioning = true)val partitioned = new ShuffledRDD[K, C, (K, C)](combined, partitioner).setSerializer(serializerClass)partitioned.mapPartitionsWithContext((context, iter) => {new InterruptibleIterator(context,bineCombinersByKey(iter, context))}, preservesPartitioning = true)} else {// Don't apply map-side combiner.val values = new ShuffledRDD[K, V, (K, V)](self, partitioner).setSerializer(serializerClass)values.mapPartitionsWithContext((context, iter) => { new InterruptibleIterator(context,bineValuesByKey(iter, context))}, preservesPartitioning = true)}}reduceByKey最终会调用combineByKey, 在这个函数中PairedRDDFunctions会被转换成为ShuffleRDD,当调用mapPartitionsWithContext之后,shuffleRDD被转换成为MapPartitionsRDDLog输出能证明我们的分析res1: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[8] at reduceByKey at :13RDD转换小结小结一下整个RDD转换过程HadoopRDD->MappedRDD->FlatMappedRDD->MappedRDD->PairRDDFunc tions->ShuffleRDD->MapPartitionsRDD整个转换过程好长啊,这一切的转换都发生在任务提交之前。