手写spark wordCount

搭建wordCount项目:

https://blog.csdn.net/py_123456/article/details/82665623

1、代码:

val conf: SparkConf = new SparkConf().setMaster(Local[*]).setAppName("wordCount")

val sc=new SparkContext(conf)

sc.textFile("/input").flatMap(" ").map((_,1)).reduceByKey(_+_).saveAsTextFile("/output")

sc.stop

val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
val sc = new SparkContext(conf)
sc.textFile("/input")
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.saveAsTextFile("/output")
sc.stop()

2、详解:


//下面是通过Spark上下文调用textFile函数创建一个包装好底层数据集的RDD对象:
//这里没有指定前缀hdfs://YunMaster01:9000,但是SparkContext(Spark上下文,即sc对象)中已经封装了这些默认的配置信息,即创建Spark上下文对象时就已经封装了这些上下文配置信息(包括操作的底层数据源,如这里的HDFS);这里最好不要加上hdfs前缀,因为加上了之后代码就被编译后固化了,造成不可配置,如果不加则SparkContext可以基于配置来自动选择(数据源类型可以通过配置来改变),提升高可维护性。
scala>val dataSet=sc.textFile("/spark/input")
//SparkContext调用textFile函数将返回一个org.apache.spark.rdd.RDD[String]类型的对象(RDD在Spark的架构源码中被Scala定义为一个接口(trait)类型;方括号中的String表示RDD集合中的元素类型为字符串类型);此RDD的具体实现是MapPartitionRDD(即:字典分区RDD);textFile操作仅仅是执行数据抽象,并没有立即执行数据的读取(read)操作,数据的读取操作会延迟到执行action动作时才发生;因此textFile函数属于一种transformation动作,transformation动作是lazy级别的操作。
//查看数据源的输入路径(如果数据源使用HDFS则显示为HDFS路径),以下如不特别加以说明默认都是基于HDFS的数据源(因为这个最常用)
//toDebugString函数会根据数据集的来源路径(RDD依赖)进行反向推理并从上到下依次显示RDD路径源列表
scala>dataSet.toDebugString() 
//返回数据集中的记录数量,这会启动一个action(textFile函数是一种转换(transformation),不会启动action);action是指Spark会在底层启动作业(Job)或任务(Task)的计算操作;而转换(transformation)仅仅是实现数据的封装和抽象(只涉及到数据在节点本地上的抽象读(注意:没有真正意义上的读,因为没有产生任何IO操作),但是数据的写操作则属于action操作,因为它涉及到IO操作过程,比如:saveAsTextFile函数)
scala>dataSet.count
//Spark中的一个分区(partition)相当于HDFS中的一个块(Block),即Spark中一个partition的尺寸等于一个HDFS文件块的尺寸(BlockSize=128M)
//下面调用flatMap函数处理dataSet集合中的每一行,此函数调用完成之后又将产生一个新的RDD对象结果集,实际上所有的Spark操作都是基于RDD集合对象的操作(一切皆为RDD操作)
scala>val dataSet02=dataSet.flatMap(_.split(" "))
//实现第二次映射操作产生新的RDD集合对象
scala>val dataSet03=dataSet02.map(word=>(word,1))
//上面的两个map算子相当于MapReduce中的map方法的操作;而下面与reduce相关的算子则相当于MapReduce中的reduce方法的操作
//由于reduce操作涉及到在节点之间转移和重新分配数据,因此此过程涉及到shuffle机制,执行reduce操作过程中产生的shuffle是为了归并相同Key的记录到同一分组以执行SQL概念中的分组统计
scala>val dataSet04=dataSet03.reduceByKey(_+_)
//从上面的执行过程来看,Spark的底层计算原理与Hadoop的底层计算原理是类同的,只是Spark在此基础上引入了RDD的概念,将一切的操作归入集合RDD的操作(实际上,Hadoop中MapReduce操作本身就是针对集合对象进行操作,只不过Hadoop没有抽象出这样一个概念而已),由于引入了RDD的概念,所以Spark可以将一切操作(应用的所有操作步骤)的输入源和输出源都抽象为RDD对象的操作,这样以来,各个操作之间就可以通过RDD对象链接成操作链(上一个操作的输出源是一个RDD对象,该RDD对象可以直接作为下一个操作的输入源)
//将应用执行过程中处理后的数据最终存储到HDFS文件系统中去(此过程是action操作)
scala>dataSet04.saveAsTextFile("/spark/output")

3、WordCount计算原理:

A、textFile函数:返回一个HadoopRDD,HadoopRDD在执行action操作计算数据时会将数据从分布式磁盘读取到分布式内存中,即从worker节点的本地磁盘读取到worker节点的本地内存中,RDD更多的是指分布式内存存储,一种数据的逻辑存储系统;
HadoopRDD产生的是一个元组集合(即集合中的每个元素是一个元组类型),元组的第一个元素是行记录的索引(Key,字节偏移量),元组的第二个元素是行记录内容本身(Value);
基于HadoopRDD会继续产生一个字符串集合MapPartitionRDD,此集合中的每个字符串代表行记录内容(没有行记录的索引,丢弃了字节偏移量Key)
B、flatMap函数:此函数仍然产生一个字符串MapPartitionRDD集合
C、map函数:此函数产生一个二元素元组类型的MapPartitionRDD集合
D、reduceByKey函数:基于相同Key的Value进行统计;先进行本地统计(执行卡宾函数统计可以减少数据传送量,降低网络负载),再进行集群模式统计(shuffle);本地统计之后的结果会根据分区策略将统计后的基于二元素元组的MapPartitionRDD集合数据写出到不同的本地文件中;在数据被传送到另一个节点之前的所有操作都是在同一个节点上的操作;在此,于同一个节点上的所有操作都是在同一个Stage中;同一个Stage中的所有操作都是基于内存的链式迭代操作(你也可以在这个迭代过程中手动缓存中间结果);我们通常说Spark是基于内存迭代的,其缘由就在于此。
E、shuffle是整个分布式计算的性能瓶颈所在,shuffle过程是从一个节点将数据传送到另一个节点的过程(即便在同一个节点上也是切换到另一个JVM进程来处理),这个过程中会针对每一份数据至少产生两次IO(本次磁盘IO、网络IO);shuffle过程是依据数据分区策略来进行的,shuffle之后将从一个Stage阶段过渡到另一个新的Stage阶段,shuffle之后将在另一个新的Stage阶段产生一个基于二元素元组的ShuffledRDD集合。
F、saveAsTextFile函数:将ShuffledRDD集合转换为MapPartitionRDD(实际上就是追加一个字节偏移量Key将每一个元组再包装成一个二元素元组(第一个元素是字节偏移量Key)),然后将其写出到HDFS文件系统中去。

参考博客:https://www.cnblogs.com/mmzs/p/8241500.html

原文地址:https://www.cnblogs.com/guoyu1/p/12068251.html