1. RDD概述
1.1 什么是RDD?
RDD (Resilient Distributed Dataset ) : 弹性分布式数据集 ,是spark中最基本的数据抽象。
-
弹性
存储:内存与磁盘的动态切换
计算:计算出错重试机制
容错:数据丢失可以自动恢复
分片:可根据需要从新分片
-
分布式
数据存储在大数据集群不同节点上
-
数据集
RDD封装了数据集,并不保存数据
-
数据抽象
RDD是个抽象类,需要子类具体实现
-
不可变
RDD封装了计算逻辑,是不可变的,需要改变,只能通过新的RDD,在新的RDD里面封装新的计算逻辑
data => RDD(计算逻辑)=》rdd(新的计算逻辑)
-
可分区并行计算
* Internally, each RDD is characterized by five main properties:
*
* - A list of partitions
* - A function for computing each split
* - A list of dependencies on other RDDs
* - Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
* - Optionally, a list of preferred locations to compute each split on (e.g. block locations for
* an HDFS file)
2. RDD创建
2.1 从集合中创建RDD
-
spark主要提供2种函数,parallelize和makeRDD
// parallelize函数 val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8)) //makeRDD函数 val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8)) -
makeRdd底层依然调用了parallelize方法
/* defaultParallelism: 创建RDD没有指定分区的时候,默认就是当前系统核数就是分区数 override def defaultParallelism(): Int = scheduler.conf.getInt("spark.default.parallelism", totalCores) */ def makeRDD[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = withScope { parallelize(seq, numSlices) }
## 2.2 从外部存储系统的数据集创建
- 本地文件系统的数据
```scala
var fileRdd: RDD[String] = sc.textFile("input")
println(fileRdd.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).collect().mkString(","))
-
Hadoop支持的数据集,包括hdfs和hbase
var hdfsRdd = sc.textFile("hdfs://mayi101:9000/wc/input/wc.txt") println(hdfsRdd.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).collect().mkString(","))
2.3 从其它RDD 创建
3. 转换算子以及分区规则
3.1 默认分区源码(RDD数据从集合中创建)
分区源码解读:
3.2 分区源码(指定分区,RDD数据从集合中创建)
//指定3个分区
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5), 3)
分区源码解读:
3.3 分区源码(从文件读取后创建)
源码
def textFile(
path: String,
minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
assertNotStopped()
hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
minPartitions).map(pair => pair._2.toString).setName(path)
}
//默认的最小分区
def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
//defaultParallelism默认就是CPU的核数
override def defaultParallelism(): Int =
scheduler.conf.getInt("spark.default.parallelism", totalCores)
//获取切片分区规划
override def getPartitions: Array[Partition] = {
val jobConf = getJobConf()
// add the credentials here as this can be called before SparkContext initialized
SparkHadoopUtil.get.addCredentials(jobConf)
val inputFormat = getInputFormat(jobConf)
val inputSplits = inputFormat.getSplits(jobConf, minPartitions)
val array = new Array[Partition](inputSplits.size)
for (i <- 0 until inputSplits.size) {
array(i) = new HadoopPartition(id, i, inputSplits(i))
}
array
}
//注意:getSplits文件返回的是切片规划,真正读取是在compute方法中创建LineRecordReader读取的,有两个关键变量
//start=split.getStart()
//end = start + split.getLength
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
//切片大小
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
long blockSize = file.getBlockSize();
//最终的切片大小
protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
/** Splits files returned by {@link #listStatus(JobConf)} when
* they're too big.*/
public InputSplit[] getSplits(JobConf job, int numSplits)
throws IOException {
StopWatch sw = new StopWatch().start();
FileStatus[] files = listStatus(job);
// Save the number of input files for metrics/loadgen
job.setLong(NUM_INPUT_FILES, files.length);
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);
// generate splits
ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
NetworkTopology clusterMap = new NetworkTopology();
for (FileStatus file: files) {
Path path = file.getPath();
long length = file.getLen();
if (length != 0) {
FileSystem fs = path.getFileSystem(job);
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(fs, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,
length-bytesRemaining, splitSize, clusterMap);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
splitHosts[0], splitHosts[1]));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations, length
- bytesRemaining, bytesRemaining, clusterMap);
splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
splitHosts[0], splitHosts[1]));
}
} else {
if (LOG.isDebugEnabled()) {
// Log only if the file is big enough to be splitted
if (length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization "
+ "is possible: " + file.getPath());
}
}
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,0,length,clusterMap);
splits.add(makeSplit(path, 0, length, splitHosts[0], splitHosts[1]));
}
} else {
//Create empty hosts array for zero length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size()
+ ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
return splits.toArray(new FileSplit[splits.size()]);
}
总结:
-
默认分区规则:
-从集合中创建RDD
取决于分配给应用的CPU的核数
-读取外部文件创建RDD
math.min(取决于分配给应用的CPU的核数,2)
-
从集合中创建分区,指定分区数
- 指定分区数
-
从文件中创建分区,指定的是最小分区数
* -默认分区规则 * math.min(分配给应用的CPU核数,2) * -指定分区 * >1.在textFile方法中,第二个参数minPartitions,表示最小分区数 * 注意:是最小,不是实际的分区个数 * >2.在实际计算分区个数的时候,**会根据文件的总大小和最小分区数进行相除运算** * &如果余数为0 * 那么最小分区数,就是最终实际的分区数 * &如果余数不为0 * 那么实际的分区数,要计算 # 说明 1.切片采用的是Hadoop的切片规则 2.RecoderReader记录读取器,默认也是按行读取,所以下面的例子,虽然切片规划是这样的,但是分区中读取的数据略微有点不一样 * * -原始数据 * 0 1 2 3 4 5 6 7 8 * a b c d e f g X X * * 9 10 11 12 13 14 * h i j k X X * * 15 16 17 18 19 * l m n X X * * 20 21 * o p * * 设置最小切片数为3 * * * 切片规划 FileInputFormat->getSplits * 0 = {FileSplit@5103} "file:/D:/dev/workspace/bigdata-0105/spark-0105/input/test.txt:0+7" * 1 = {FileSplit@5141} "file:/D:/dev/workspace/bigdata-0105/spark-0105/input/test.txt:7+7" * 2 = {FileSplit@5181} "file:/D:/dev/workspace/bigdata-0105/spark-0105/input/test.txt:14+7" * 3 = {FileSplit@5237} "file:/D:/dev/workspace/bigdata-0105/spark-0105/input/test.txt:21+1" * * * 最终分区数据 * 0 分区 * abcdefg * 1 分区 * hijk * 2 分区 * lmn * op * 3 分区 * 空切片源码:
// totalSize:文件大小,numSplits指定的最小分区数 //计算每个分区放入的数据大小goalSize long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits); //分区的最小大小,这个我们可以自己指定 long minSize = Math.max(job.getLong("mapreduce.input.fileinputformat.split.minsize", 1L), this.minSplitSize); //块大小 long blockSize = file.getBlockSize(); //正在计算切片的大小 long splitSize = this.computeSplitSize(goalSize, minSize, blockSize); //计算切片大小的算法 protected long computeSplitSize(long goalSize, long minSize, long blockSize) { return Math.max(minSize, Math.min(goalSize, blockSize)); } //形成切片规划文件的算法 long bytesRemaining = length; while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,length-bytesRemaining, splitSize, clusterMap); splits.add(makeSplit(path, length-bytesRemaining, splitSize,splitHosts[0], splitHosts[1])); bytesRemaining -= splitSize; } //通过文件,存储系统创建RDD sc.textFile() //Hadoop 获取之前的分区规划信息,创建分区,从这里也能看到一个切片一个分区 override def getPartitions: Array[Partition] = { val jobConf = getJobConf() // add the credentials here as this can be called before SparkContext initialized SparkHadoopUtil.get.addCredentials(jobConf) val inputFormat = getInputFormat(jobConf) //这个就是上面说的获取的切片规划信息 val inputSplits = inputFormat.getSplits(jobConf, minPartitions) val array = new Array[Partition](inputSplits.size) for (i <- 0 until inputSplits.size) { //创建分区 array(i) = new HadoopPartition(id, i, inputSplits(i)) } array } //分区计算逻辑,知道分区中有哪些东西, override def compute(theSplit: Partition, context: TaskContext): InterruptibleIterator[(K, V)] = { private def updateBytesRead(): Unit = { getBytesReadCallback.foreach { getBytesRead => inputMetrics.setBytesRead(existingBytesRead + getBytesRead()) } } //记录读取器 private var reader: RecordReader[K, V] = null private val inputFormat = getInputFormat(jobConf) HadoopRDD.addLocalConfiguration( new SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(createTime), context.stageId, theSplit.index, context.attemptNumber, jobConf) reader = try { //获取recorderReader inputFormat.getRecordReader(split.inputSplit.value, jobConf, Reporter.NULL) } catch { case e: IOException if ignoreCorruptFiles => logWarning(s"Skipped the rest content in the corrupted file: ${split.inputSplit}", e) finished = true null } }练习:
1.一份文件a,内容如下 0 1 2 3 4 a b c X X 5 6 7 8 e f X X 9 10 11 g X X 12 13 14 15 h j X X 16 17 18 k l m 假设设置最小分区数是5,那么,实际分区是多少,每个分区的内容是什么? 答案: //实际分区是: 实际分区应该是7个 //分析 //a.当前的a文件总大小19 每一行,默认是有换行符+回车符(最后一行没有这两个),也就是相当于每行多2个字节,所以上面文件a,应该是11+8=19 goalSize= totalSize/numSplites => 19/5 = 3 blockSize:默认32M minSize=1 根据算法:Math.max(minSize,Math.min(goalSize,blockSize))=>splitSize= 3 //b.获取到splitSize3,规划分区就可以开始计算了 最小分区是5, totalSize - minPartition*splitSize = 4 //还剩下4个长度内容没有规划切片分区 4/3 = 1 ,因为有余数,所以应该是1 + 1=2个分区 ===》经过上面的推导,共计5+2 = 7个分区。 //分区内容如下: 0号分区:0+3 abc 1号分区:3+3 ef 2号分区:6+3 g 3号分区:9+3 hj 4号分区:12+3 klm 5号分区:15+3 空 6号分区:18+3 空 2. abcde 最小分区数是3,实际分区是多少? 3. 有一个文本大小37个字节,最小分区数是2,实际分区数是多少?
4.1 Value类型
4.1.1 map映射
分析图
具体实现
object value01_map {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc = new SparkContext(conf)
//3具体业务逻辑
// 3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
// 3.2 调用map方法,每个元素乘以2
val mapRdd: RDD[Int] = rdd.map(_ * 2)
// 3.3 打印修改后的RDD中数据
mapRdd.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
4.1.2 mapPartitions以分区为单位执行map
mapPartitions算子
//以分区为单位,对RDD中的元素进行映射
//一般适用于批处理的操作,比如:将RDD中的元素插入到数据库中,需要数据库连接,
// 如果每一个元素都创建一个连接,效率很低;可以对每个分区的元素,创建一个连接
分析图
案例
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
// 3.2 调用mapPartitions方法,每个元素乘以2
val rdd1 = rdd.mapPartitions(x=>x.map(_*2))
// 3.3 打印修改后的RDD中数据
rdd1.collect().foreach(println)
4.1.3 map与mapPartitions的区别
4.1.4 mapPartitionsWithIndex 带分区号
//3具体业务逻辑
// 3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
// 3.2 创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD
val indexRdd = rdd.mapPartitionsWithIndex( (index,items)=>{items.map( (index,_) )} )
//扩展功能:第二个分区元素*2,其余分区不变
// 3.3 打印修改后的RDD中数据
indexRdd.collect().foreach(println)
map相关(个人理解):
无论是map,mapPartition,还是mapPartitionsWithIndex(),经过参数中定义的方法映射处理以后,最后的返回结果都是一个iterator迭代器,经过map的时候,map每次处理一个元素时,都会将处理后的元素放到一个迭代器中,当遍历结束后,迭代器就是最终的结果。
4.1.5 flatmap 压平
个人理解:
flatmap偏平化的处理,适合于所有集合里面的元素,每个元素还是集合,然后将子元素集合中所有的元素全部放到一个集合中,让子集合中的元素时单纯的元素,不在是集合。相当于敲破所有子元素集合,把子元素集合的元素弄出来。
原理图
实例
//3具体业务逻辑
// 3.1 创建一个RDD
val listRDD=sc.makeRDD(List(List(1,2),List(3,4),List(5,6),List(7)), 2)
// 3.2 把所有子集合中数据取出放入到一个大的集合中
listRDD.flatMap(list=>list).collect.foreach(println)
4.1.6 glom分区转换数组
原理图
实例
//3具体业务逻辑
// 3.1 创建一个RDD
val rdd = sc.makeRDD(1 to 4, 2)
// 3.2 求出每个分区的最大值 0->1,2 1->3,4
val maxRdd: RDD[Int] = rdd.glom().map(_.max)
// 3.3 求出所有分区的最大值的和 2 + 4
println(maxRdd.collect().sum)
4.1.7 groupBy分组
个人理解
groupBy:按照传入函数的返回值进行分组,groupBy的参数,相当于是定义一个分组规则
// val rdd: RDD[Int] = sc.makeRDD(1 to 10, 3)
// println(rdd.groupBy(_%2).collect().mkString(","))
// (0,CompactBuffer(2, 4, 6, 8, 10)),(1,CompactBuffer(1, 3, 5, 7, 9))
val rdd = sc.makeRDD(Array("江西 南昌 上饶", "河北 石家庄 大同 上饶","南昌 大同 上饶"),3)
val flatMapRdd = rdd.flatMap(_.split(" ")).map((_,1))
// val str: String = flatMapRdd.groupBy(_.contains("上饶")).collect().mkString(",")
val str: String = flatMapRdd.groupBy(_._1).collect().mkString(",")
/*
(河北,CompactBuffer((河北,1))),(石家庄,CompactBuffer((石家庄,1))),(南昌,CompactBuffer((南昌,1), (南昌,1))),(江西,CompactBuffer((江西,1))),(上饶,CompactBuffer((上饶,1), (上饶,1), (上饶,1))),(大同,CompactBuffer((大同,1), (大同,1)))
*/
原理图
实例
// 3.1 创建一个RDD
val rdd = sc.makeRDD(1 to 4, 2)
// 3.2 将每个分区的数据放到一个数组并收集到Driver端打印
rdd.groupBy(_ % 2).collect().foreach(println)
// 3.3 创建一个RDD
val rdd1: RDD[String] = sc.makeRDD(List("hello","hive","hadoop","spark","scala"))
// 3.4 按照首字母第一个单词相同分组
rdd1.groupBy(str=>str.substring(0,1)).collect().foreach(println)
**备注:**groupBy会存在shuffle的过程
shuffle:将不同的分区数据打乱重组的过程,shuffle一定会落盘
4.1.8 groupBy之wordCount
// 3.1 创建一个RDD
val strList: List[String] = List("Hello Scala", "Hello Spark", "Hello World")
val rdd = sc.makeRDD(strList)
// 3.2 将字符串拆分成一个一个的单词
val wordRdd: RDD[String] = rdd.flatMap(str=>str.split(" "))
// 3.3 将单词结果进行转换:word=>(word,1)
val wordToOneRdd: RDD[(String, Int)] = wordRdd.map(word=>(word, 1))
// 3.4 将转换结构后的数据分组
val groupRdd: RDD[(String, Iterable[(String, Int)])] = wordToOneRdd.groupBy(t=>t._1)
// 3.5 将分组后的数据进行结构的转换
val wordToSum: RDD[(String, Int)] = groupRdd.map {
case (word, list) => {
(word, list.size)
}
}
wordToSum.collect().foreach(println)
复杂版的wordCount
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("Hello Scala, 2), ("Hello Spark, 3), (Hello World, 2)))
//以下方式是比较好的一种实现方式
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2)))
//对RDD中的元素进行扁平映射
val flatMapRDD: RDD[(String, Int)] = rdd.flatMap {
case (words, count) => {
words.split(" ").map(word => (word, count))
}
}
//按照单词对RDD中的元素进行分组 (Hello,CompactBuffer((Hello,2), (Hello,3), (Hello,2)))
val groupByRDD: RDD[(String, Iterable[(String, Int)])] = flatMapRDD.groupBy(_._1)
//对RDD的元素重新进行映射
val resRDD: RDD[(String, Int)] = groupByRDD.map {
case (word, datas) => {
(word, datas.map(_._2).sum)
}
}
resRDD.collect().foreach(println)
4.1.9 filter 算子
个人理解
filter其实和groupBy是类似的,groupBy是按照参数中的函数规则,对元素进行分类,filter也是利用每个元素,和定义的方法规则,如果满足规则,也就是true,这些元素将会留下,放进新的RDD中,否则丢弃不满足条件的数据
原理图
实例:
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4),2)
//过滤出符合条件的数据
val filterRdd: RDD[Int] = rdd.filter(_ % 2 == 0)
4.1.10 sample 采样 ?
原理图
实例
// 3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 10)
// 3.2 打印放回抽样结果
rdd.sample(true, 0.4, 2).collect().foreach(println)
// 3.3 打印不放回抽样结果
rdd.sample(false, 0.2, 3).collect().foreach(println)
扩展小结:
object Transformation_sample {
def main(args: Array[String]): Unit = {
//创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//val rdd: RDD[Int] = sc.makeRDD(1 to 10)
/*
-withReplacement 是否抽样放回
true 抽样放回
false 抽样不放回
-fraction
withReplacement=true 表示期望每一个元素出现的次数 >0
withReplacement=false 表示RDD中每一个元素出现的概率[0,1]
-seed 抽样算法初始值
一般不需要指定
*/
//从rdd中随机抽取一些数据(抽样放回)
//val newRDD: RDD[Int] = rdd.sample(true,3)
//从rdd中随机抽取一些数据(抽样不放回)
//val newRDD: RDD[Int] = rdd.sample(false,0.6)
//newRDD.collect().foreach(println)
val stds: List[String] = List("张三","李四","王五")
val nameRDD: RDD[String] = sc.makeRDD(stds)
//从上面RDD中抽取一名幸运同学进行连麦
val luckyMan: Array[String] = nameRDD.takeSample(false,1)
luckyMan.foreach(println)
// 关闭连接
sc.stop()
}
}
4.1.11 distinct 算子
源码
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)
}
原理图
实例
// 3.1 创建一个RDD
val distinctRdd: RDD[Int] = sc.makeRDD(List(1,2,1,5,2,9,6,1))
// 3.2 打印去重后生成的新RDD
distinctRdd.distinct().collect().foreach(println)
// 3.3 对RDD采用多个Task去重,提高并发度
distinctRdd.distinct(2).collect().foreach(println)
4.1.12 coalesce 重新分区
源码:
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T] = withScope {
require(numPartitions > 0, s"Number of partitions ($numPartitions) must be positive.")
//默认是不执行shuffle,一般用于缩减分区
if (shuffle) {
/** Distributes elements evenly across output partitions, starting from a random partition. */
val distributePartition = (index: Int, items: Iterator[T]) => {
var position = (new Random(index)).nextInt(numPartitions)
items.map { t =>
// Note that the hash code of the key will just be the key itself. The HashPartitioner
// will mod it with the number of total partitions.
position = position + 1
(position, t)
}
} : Iterator[(Int, T)]
// include a shuffle step so that our upstream tasks are still distributed
new CoalescedRDD(
new ShuffledRDD[Int, T, T](mapPartitionsWithIndex(distributePartition),
new HashPartitioner(numPartitions)),
numPartitions,
partitionCoalescer).values
} else {
new CoalescedRDD(this, numPartitions, partitionCoalescer)
}
}
小结:
-
-coalesce
默认是不执行shuffle,一般用于缩减分区
-
-repartition
底层调用的就是coalesce,只不过默认是执行shuffle,一般用于扩大分区
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope { coalesce(numPartitions, shuffle = true) }
//缩减分区 //val newRDD: RDD[Int] = numRDD.coalesce(2)
//扩大分区 //注意:默认情况下,如果使用coalesce扩大分区是不起作用的 。因为底层没有执行shuffle //val newRDD: RDD[Int] = numRDD.coalesce(4)
Coalesce算子包括:配置执行Shuffle和配置不执行Shuffle两种方式。
4.1.12.1 不执行shuffle方式
//3.创建一个RDD
//val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 4)
//3.1 缩减分区
//val coalesceRdd: RDD[Int] = rdd.coalesce(2)
//4. 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//4.1 缩减分区
val coalesceRdd: RDD[Int] = rdd.coalesce(2)
4.1.12.2 执行shuffle方式
//3. 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//3.1 执行shuffle
val coalesceRdd: RDD[Int] = rdd.coalesce(2, true)
4.1.12.3 shuffle
4.1.13 repartition 重新分区
//3. 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//3.1 缩减分区
//val coalesceRdd: RDD[Int] = rdd.coalesce(2,true)
//3.2 重新分区
val repartitionRdd: RDD[Int] = rdd.repartition(2)
4.1.14 coalesce和repairtition的区别
1)coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。
2)repartition实际上是调用的coalesce,进行shuffle。源码如下:
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}
3)coalesce一般为缩减分区,如果扩大分区,不使用shuffle是没有意义的,repartition扩大分区执行shuffle。
4.1.15 sortBy排序
原理图
sortByKey:
按照k做排序,默认是正序,true-正序,false-倒叙
利用sortByKey倒序
val rdd = sc.makeRDD(List("java is best php is scala is best language"))
val tuples = rdd.flatMap(x => {
x.split(" ").map((_, 1))
}).groupBy(_._1).map({
case (index, datas) => {
(index, datas.size)
}
}).map(x=>(x._2,x._1)).sortByKey(false)
实例
//3具体业务逻辑
// 3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(2, 1, 3, 4, 6, 5))
// 3.2 默认是升序排
val sortRdd: RDD[Int] = rdd.sortBy(num => num)
sortRdd.collect().foreach(println)
// 3.3 配置为倒序排
val sortRdd2: RDD[Int] = rdd.sortBy(num => num, false)
sortRdd2.collect().foreach(println)
// 3.4 创建一个RDD
val strRdd: RDD[String] = sc.makeRDD(List("1", "22", "12", "2", "3"))
// 3.5 按照字符的int值排序
strRdd.sortBy(num => num.toInt).collect().foreach(println)
// 3.5 创建一个RDD
val rdd3: RDD[(Int, Int)] = sc.makeRDD(List((2, 1), (1, 2), (1, 1), (2, 2)))
// 3.6 先按照tuple的第一个值排序,相等再按照第2个值排
rdd3.sortBy(t=>t).collect().foreach(println)
4.1.16 pipe调用脚本
4.2 双Value类型交互
4.2.1 union 并集
//3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd2: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 计算两个RDD的并集
rdd1.union(rdd2).collect().foreach(println)
4.2.2 subtract 差集
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd1: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 计算第一个RDD与第二个RDD的差集并打印
rdd.subtract(rdd1).collect().foreach(println)
4.2.3 intersection 交集
//3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd2: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 计算第一个RDD与第二个RDD的差集并打印
rdd1.intersection(rdd2).collect().foreach(println)
4.2.4 zip拉链
//3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD(Array(1,2,3),3)
//3.2 创建第二个RDD
val rdd2: RDD[String] = sc.makeRDD(Array("a","b","c"),3)
//3.3 第一个RDD组合第二个RDD并打印
rdd1.zip(rdd2).collect().foreach(println)
//3.4 第二个RDD组合第一个RDD并打印
rdd2.zip(rdd1).collect().foreach(println)
//3.5 创建第三个RDD(与1,2分区数不同)
val rdd3: RDD[String] = sc.makeRDD(Array("a","b"),3)
//3.6 元素个数不同,不能拉链
// Can only zip RDDs with same number of elements in each partition
rdd1.zip(rdd3).collect().foreach(println)
//3.7 创建第四个RDD(与1,2分区数不同)
val rdd4: RDD[String] = sc.makeRDD(Array("a","b","c"),2)
//3.8 分区数不同,不能拉链
// Can't zip RDDs with unequal numbers of partitions: List(3, 2)
rdd1.zip(rdd4).collect().foreach(println)
备注:
元素个数不同,不能拉链
分区数不同,不能拉链
小结:
val rdd = sc.makeRDD(1 to 4)
val rdd1 = sc.makeRDD(3 to 6)
// println(rdd.union(rdd1).collect().mkString(",")) //并集 2个RDD所有元素
// println(rdd.subtract(rdd1).collect().mkString(",")) //差集 rdd-rdd1,rdd剩余元素
// println(rdd.intersection(rdd1).collect().mkString(",")) //交集 rdd与rdd1都有的元素
println(rdd.zip(rdd1).collect().mkString(",")) //拉链 组成(k,v)
4.3 key-value类型
4.3.1 partitionBy 按k重新分区
0)理解:
默认分区器是HashPartitioner,自定义的分区器,需要继承Partitioner,重写getPartition方法,该方法就是定义key按照里面的规则分配到指定的分区,该方法返回值就是分区号,也就是对应key分配的分区。
1)原理图
2)实例
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"aaa"),(2,"bbb"),(3,"ccc")),3)
//3.2 对RDD重新分区
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))
//3.3 查看新RDD的分区数
println(rdd2.partitions.size)
3)HashPartitioner源码解读:
class HashPartitioner(partitions: Int) extends Partitioner {
require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")
def numPartitions: Int = partitions
def getPartition(key: Any): Int = key match {
case null => 0
case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
}
override def equals(other: Any): Boolean = other match {
case h: HashPartitioner =>
h.numPartitions == numPartitions
case _ =>
false
}
override def hashCode: Int = numPartitions
}
4) 自定义分区器
object KeyValue01_partitionBy {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)
//3.2 自定义分区
val rdd3: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(2))
//4 打印查看对应分区数据
val indexRdd: RDD[(Int, String)] = rdd3.mapPartitionsWithIndex(
(index, datas) => {
// 打印每个分区数据,并带分区号
datas.foreach(data => {
println(index + "=>" + data)
})
// 返回分区的数据
datas
}
)
indexRdd.collect()
//5.关闭连接
sc.stop()
}
}
// 自定义分区
class MyPartitioner(num: Int) extends Partitioner {
// 设置的分区数
override def numPartitions: Int = num
// 具体分区逻辑
override def getPartition(key: Any): Int = {
if (key.isInstanceOf[Int]) {
val keyInt: Int = key.asInstanceOf[Int]
if (keyInt % 2 == 0)
0
else
1
}else{
0
}
}
}
4.3.2 reduceByKey 按K重新分区
原理图
看源码可以知道,它底层调用的是combineByKeyWithClassTag,并有combiner操作,分区内和分区间执行相同的业务逻辑。
/* - `createCombiner`, which turns a V into a C (e.g., creates a one-element list)
* - `mergeValue`, to merge a V into a C (e.g., adds it to the end of a list)
* - `mergeCombiners`, to combine two C's into a single one.
*/
案例:
//3.1 创建第一个RDD
val rdd = sc.makeRDD(List(("a",1),("b",5),("a",5),("b",2)))
//3.2 计算相同key对应值的相加结果
val reduce: RDD[(String, Int)] = rdd.reduceByKey((x,y) => x+y)
//3.3 打印结果
reduce.collect().foreach(println
4.3.3 groupByKey 按k重新分组
原理图
看源码可以得出:
它没有进行combiner操作,分区内相同key的v,放进一个CompactBuffer里面,分区间的CompactBuffer进行合并,组成一个大的CompactBuffer,而且CompactBuffer放的都是每个key里面的value
需求说明:创建一个pairRDD,将相同key对应值聚合到一个seq中,并计算相同key对应值的相加结果
//3.1 创建第一个RDD
val rdd = sc.makeRDD(List(("a",1),("b",5),("a",5),("b",2)))
//3.2 将相同key对应值聚合到一个Seq中
val group: RDD[(String, Iterable[Int])] = rdd.groupByKey()
//3.3 打印结果
group.collect().foreach(println)
//3.4 计算相同key对应值的相加结果
group.map(t=>(t._1,t._2.sum)).collect().foreach(println)
4.3.4 reduceByKey与groupByKey的区别
1)reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
2)groupByKey:按照key进行分组,直接进行shuffle。
3)开发指导:在不影响业务逻辑的前提下,优先选用reduceByKey。求和操作不影响业务逻辑,求平均值影响业务逻辑。
4.3.5 aggregateByKey 分区内与分区间
按照key处理分区内和分区间的逻辑
原理分析图
案例分析
//3.1 创建第一个RDD
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
//3.2 取出每个分区相同key对应值的最大值,然后相加
rdd.aggregateByKey(0)(math.max(_, _), _ + _).collect().foreach(println)
4.3.6 foldByKey 分区内与分区间相同
和aggregateByKey不同的是,它分区内与分区间的逻辑是相同的
实例:
//3.1 创建第一个RDD
val list: List[(String, Int)] = List(("a",1),("a",1),("a",1),("b",1),("b",1),("b",1),("b",1),("a",1))
val rdd = sc.makeRDD(list,2)
//3.2 求wordcount
//rdd.aggregateByKey(0)(_+_,_+_).collect().foreach(println)
rdd.foldByKey(0)(_+_).collect().foreach(println)
4.3.7 combinByKey
不好理解,要多去理解
原理
案例分析
需求说明:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key对应值的总和以及key出现的次数,再相除得到结果)
//3.1 创建第一个RDD
val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
val input: RDD[(String, Int)] = sc.makeRDD(list, 2)
//3.2 将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
(_, 1),
(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
//3.3 打印合并后的结果
combineRdd.collect().foreach(println)
//3.4 计算平均值
combineRdd.map {
case (key, value) => {
(key, value._1 / value._2.toDouble)
}
}.collect().foreach(println)
4.3.8 上面几个聚合的区别
reduceByKey, aggregateByKey, foldByKey和combinByKey
4.3.9 sortByKey 按k排序
实例:
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
//3.2 按照key的正序(默认顺序)
rdd.sortByKey(true).collect().foreach(println)
//3.3 按照key的倒序
rdd.sortByKey(false).collect().foreach(println)
4.3.10 mapValues 只对v进行操作
相当于对一个RDD数据集进行遍历,对每个元素中的value做操作
原理图
实例
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))
//3.2 对value添加字符串"|||"
rdd.mapValues(_ + "|||").collect().foreach(println)
4.3.11 join 连接
将相同key对应的多个value关联在一起
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
//3.2 创建第二个pairRDD
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))
//3.3 join操作并打印结果
rdd.join(rdd1).collect().foreach(println)
4.3.12 cogroupBy
类似全连接,但是在同一个RDD中对key进行聚合
操作两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c")))
//3.2 创建第二个RDD
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(3,6)))
//3.3 cogroup两个RDD并打印结果
rdd.cogroup(rdd1).collect().foreach(println)
4.4 案例操作(广告点击top3)
测试数据:链接: pan.baidu.com/s/14qwNw9Is… 提取码: 9mbr
实现
object Demo_top3 {
def main(args: Array[String]): Unit = {
//1. 初始化Spark配置信息并建立与Spark的连接
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Test")
val sc = new SparkContext(sparkConf)
//2. 读取日志文件,获取原始数据
val dataRDD: RDD[String] = sc.textFile("input/agent.log")
//3. 将原始数据进行结构转换string =>(prv-adv,1)
val prvAndAdvToOneRDD: RDD[(String, Int)] = dataRDD.map {
line => {
val datas: Array[String] = line.split(" ")
(datas(1) + "-" + datas(4), 1)
}
}
//4. 将转换结构后的数据进行聚合统计(prv-adv,1)=>(prv-adv,sum)
val prvAndAdvToSumRDD: RDD[(String, Int)] = prvAndAdvToOneRDD.reduceByKey(_ + _)
//5. 将统计的结果进行结构的转换(prv-adv,sum)=>(prv,(adv,sum))
val prvToAdvAndSumRDD: RDD[(String, (String, Int))] = prvAndAdvToSumRDD.map {
case (prvAndAdv, sum) => {
val ks: Array[String] = prvAndAdv.split("-")
(ks(0), (ks(1), sum))
}
}
//6. 根据省份对数据进行分组:(prv,(adv,sum)) => (prv, Iterator[(adv,sum)])
val groupRDD: RDD[(String, Iterable[(String, Int)])] = prvToAdvAndSumRDD.groupByKey()
//7. 对相同省份中的广告进行排序(降序),取前三名
val mapValuesRDD: RDD[(String, List[(String, Int)])] = groupRDD.mapValues {
datas => {
datas.toList.sortWith(
(left, right) => {
left._2 > right._2
}
).take(3)
}
}
//8. 将结果打印
mapValuesRDD.collect().foreach(println)
//9.关闭与spark的连接
sc.stop()
}
}
5 . Action算子
行动算子是触发了整个作业的执行。因为转换算子都是懒加载,并不会立即执行。
5.1 reduce 聚合
1)函数签名:def reduce(f: (T, T) => T): T
2)功能说明:f函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
//需求说明:创建一个RDD,将所有元素聚合得到结果
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//3.2 聚合数据
val reduceResult: Int = rdd.reduce(_+_)
println(reduceResult)
5.2 collect 以数组的形式返回数据集
1)函数签名:def collect(): Array[T]
2)功能说明:在驱动程序中,以数组Array的形式返回数据集的所有元素。
//需求说明:创建一个RDD,并将RDD内容收集到Driver端打印
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//3.2 收集数据到Driver
rdd.collect().foreach(println)
5.3 count 返回RDD中元素个数
//统计改RDD的个数
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//3.2 返回RDD中元素的个数
val countResult: Long = rdd.count()
println(countResult)
5.4 first 返回RDD中第一个元素
1)函数签名: def first(): T
2)功能说明:返回RDD中的第一个元素
5.5 take 返回RDD中前N个元素
1)函数签名: def take(num: Int): Array[T]
2)功能说明:返回一个由RDD的前n个元素组成的数组
//取前2的元素
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//3.2 返回RDD中元素的个数
val takeResult: Array[Int] = rdd.take(2)
5.6 takeOrdered
返回该RDD排序后的前N个元素组成的数组
1)函数签名: def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
2)功能说明:返回该RDD排序后的前n个元素组成的数组
源代码定义
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] = withScope {
......
if (mapRDDs.partitions.length == 0) {
Array.empty
} else {
mapRDDs.reduce { (queue1, queue2) =>
queue1 ++= queue2
queue1
}.toArray.sorted(ord)
}
}
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))
//3.2 返回RDD中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
5.7 aggregate
//需求说明:创建一个RDD,将所有元素相加得到结果
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),8)
//3.2 将该RDD所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _)
val result: Int = rdd.aggregate(10)(_ + _, _ + _)
5.8 fold
//需求说明:创建一个RDD,将所有元素相加得到结果
//3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
//3.2 将该RDD所有元素相加得到结果
val foldResult: Int = rdd.fold(0)(_+_)
println(foldResult)
5.9 countByKey 统计每个key的个数
1)函数签名:def countByKey(): Map[K, Long]
2)功能说明:统计每种key的个数
//需求说明:创建一个PairRDD,统计每种key的个数
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))
//3.2 统计每种key的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
println(result)
5.10 save相关算子
- saveAsTextFile(path)保存成Text文件
(1)函数签名
(2)功能说明:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
2)保存成Sequencefile文件
(1)函数签名
(2)功能说明:将数据集中的元素以Hadoop Sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
注意:只有kv类型RDD有该操作,单值的没有
3)saveAsObjectFile(path)序列化成对象保存到文件
(1)函数签名
(2)功能说明:用于将RDD中的元素序列化成对象,存储到文件中。
5.11 foreach 遍历RDD中每个元素

//需求说明:创建一个RDD,对每个元素进行打印
//3.1 创建第一个RDD
// val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//3.2 收集后打印
rdd.map(num=>num).collect().foreach(println)
println("****************")
//3.3 分布式打印
rdd.foreach(println)
6. RDD序列化
在实际开发中我们往往需要自己定义一些对于RDD的操作,那么此时需要注意的是,初始化工作是在Driver端进行的,而实际运行程序是在Executor端进行的,这就涉及到了跨进程通信,是需要序列化的。下面我们看几个例子:
6.1 闭包引入
object serializable01_object {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.创建两个对象
val user1 = new User()
user1.name = "zhangsan"
val user2 = new User()
user2.name = "lisi"
val userRDD1: RDD[User] = sc.makeRDD(List(user1, user2))
//3.1 打印,ERROR报java.io.NotSerializableException
//userRDD1.foreach(user => println(user.name))
//3.2 打印,RIGHT
val userRDD2: RDD[User] = sc.makeRDD(List())
//userRDD2.foreach(user => println(user.name))
//3.3 打印,ERROR Task not serializable 注意:没执行就报错了
userRDD2.foreach(user => println(user1.name))
//4.关闭连接
sc.stop()
}
}
//class User {
// var name: String = _
//}
class User extends Serializable {
var name: String = _
}
闭包检查源码
6.2 序列化方法和属性
1)说明
Driver:算子以外的代码都是在Driver端执行
Executor:算子里面的代码都是在Executor端执行
6.3 kryo 序列化框架
序列化,详细查看doc文档
7. RDD依赖关系
7.1 查看血缘关系
RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
7.1.1 血缘关系查看图
7.1.2 实例
val fileRDD: RDD[String] = sc.textFile("input/1.txt")
println(fileRDD.toDebugString)
println("----------------------")
val wordRDD: RDD[String] = fileRDD.flatMap(_.split(" "))
println(wordRDD.toDebugString)
println("----------------------")
val mapRDD: RDD[(String, Int)] = wordRDD.map((_,1))
println(mapRDD.toDebugString)
println("----------------------")
val resultRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_+_)
println(resultRDD.toDebugString)
结果:
注意:圆括号中的数字表示RDD的并行度,也就是有几个分区
(2) input/1.txt MapPartitionsRDD[1] at textFile at Lineage01.scala:15 []
| input/1.txt HadoopRDD[0] at textFile at Lineage01.scala:15 []
----------------------
(2) MapPartitionsRDD[2] at flatMap at Lineage01.scala:19 []
| input/1.txt MapPartitionsRDD[1] at textFile at Lineage01.scala:15 []
| input/1.txt HadoopRDD[0] at textFile at Lineage01.scala:15 []
----------------------
(2) MapPartitionsRDD[3] at map at Lineage01.scala:23 []
| MapPartitionsRDD[2] at flatMap at Lineage01.scala:19 []
| input/1.txt MapPartitionsRDD[1] at textFile at Lineage01.scala:15 []
| input/1.txt HadoopRDD[0] at textFile at Lineage01.scala:15 []
----------------------
(2) ShuffledRDD[4] at reduceByKey at Lineage01.scala:27 []
+-(2) MapPartitionsRDD[3] at map at Lineage01.scala:23 []
| MapPartitionsRDD[2] at flatMap at Lineage01.scala:19 []
| input/1.txt MapPartitionsRDD[1] at textFile at Lineage01.scala:15 []
| input/1.txt HadoopRDD[0] at textFile at Lineage01.scala:15 []
7.2 查看依赖关系
val fileRDD: RDD[String] = sc.textFile("input/1.txt")
println(fileRDD.dependencies)
println("----------------------")
val wordRDD: RDD[String] = fileRDD.flatMap(_.split(" "))
println(wordRDD.dependencies)
println("----------------------")
val mapRDD: RDD[(String, Int)] = wordRDD.map((_,1))
println(mapRDD.dependencies)
println("----------------------")
val resultRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_+_)
println(resultRDD.dependencies)
结果:
List(org.apache.spark.OneToOneDependency@f2ce6b)
----------------------
List(org.apache.spark.OneToOneDependency@692fd26)
----------------------
List(org.apache.spark.OneToOneDependency@627d8516)
----------------------
List(org.apache.spark.ShuffleDependency@a518813)
OneToOneDependency定义:
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
override def getParents(partitionId: Int): List[Int] = List(partitionId)
}
7.3 窄依赖
父RDD的每一个partition被子的一个partition处理就是窄依赖,父的每一个partition,如何在子RDD中,被子用多个分区来处理父的那个一个partition,那么就是宽依赖
7.4 宽依赖
7.5 spark中的job调度
一个Spark应用包含一个驱动进程(driver process,在这个进程中写Spark的逻辑代码)和多个执行器进程(executor process,跨越集群中的多个节点)。Spark 程序自己是运行在驱动节点, 然后发送指令到执行器节点。
一个Spark集群可以同时运行多个Spark应用, 这些应用是由集群管理器(cluster manager)来调度。
Spark应用可以并发的运行多个job, job对应着给定的应用内的在RDD上的每个 action操作。
7.5.1 spark应用
a) 一个Spark应用可以包含多个Spark job, Spark job是在驱动程序中由SparkContext 来定义的。
b) 当启动一个 SparkContext 的时候, 就开启了一个 Spark 应用。 一个驱动程序被启动了, 多个执行器在集群中的多个工作节点(worker nodes)也被启动了。 一个执行器就是一个 JVM, 一个执行器不能跨越多个节点, 但是一个节点可以包括多个执行器。
c ) 一个 RDD 会跨多个执行器被并行计算. 每个执行器可以有这个 RDD 的多个分区, 但是一个分区不能跨越多个执行器.
7.5.2 Spark Job的划分
a) 由于Spark的懒执行, 在驱动程序调用一个action之前, Spark 应用不会做任何事情,针对每个action,Spark 调度器就创建一个执行图(execution graph)和启动一个 Spark job。
b) 每个 job 由多个stages 组成, 这些 stages 就是实现最终的 RDD 所需的数据转换的步骤。一个宽依赖划分一个stage。每个 stage 由多个 tasks 来组成, 这些 tasks 就表示每个并行计算, 并且会在多个执行器上执行。
7.6 stage任务划分
1)DAG有向无环图
2)RDD任务切分中间分为:Application、Job、Stage和Task
(1)Application:初始化一个SparkContext即生成一个Application;
(2)Job:一个Action算子就会生成一个Job;
(3)Stage:Stage等于宽依赖的个数加1;
(4)Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。
注意:Application->Job->Stage->Task每一层都是1对n的关系。
实例
//3. 创建RDD
val dataRDD: RDD[Int] = sc.makeRDD(List(1,2,3,4,1,2),2)
//3.1 聚合
val resultRDD: RDD[(Int, Int)] = dataRDD.map((_,1)).reduceByKey(_+_)
// Job:一个Action算子就会生成一个Job;
//3.2 job1打印到控制台
resultRDD.collect().foreach(println)
//3.3 job2输出到磁盘
resultRDD.saveAsTextFile("output")
Thread.sleep(1000000)
任务划分以及源码分析: