package com.atguigu.Spark.RDD import org.apache.spark.rdd.RDD import org.apache.spark.storage.StorageLevel import org.apache.spark.util.AccumulatorV2 import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer /** * Create by zgn on 2021/12/1 18:11 * **/ object RDD_Instance_Memory { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") // 自己写的配置的优先级大于环境 conf.set("spark.default.parallelism", "4") //设置线程数并行数 val sc = new SparkContext(conf) // TODO 从内存(集合)中创建RDD // parallelize( 平行放置;使……平行于……;使程序(适合)进行计算)方法用于构建rdd // 也可以将这个集合当成数据模型处理的数据源 val Rdd = sc.parallelize( //并行 Seq(1, 2, 3, 4) ) // 使用下面的方法创建更加清晰 见名知义 val rdd1 = sc.makeRDD(// 底层调用了 parallelize(seq, numSlices) Seq(1, 2, 3, 4) ) // TODO totalCores 当前Master总的核数 } def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local").setAppName("RDD") conf.set("spark.default.parallelism","4")//设置线程的并行数 val sc = new SparkContext(conf) /*TODO 从内存集合中创建RDD, parallelize方法用于构建RDD平行放置 也可以将这个集合当成数据模型处理 * 的数据源*/ val Rdd: RDD[Int] = sc.parallelize(Seq(1,2,3,4,5,6)) /*TODO 使用下面的方法创建更加清晰*/ val rdd1: RDD[Int] = sc.makeRDD(Seq(12,3,4,5,6)) /*TODO totalCores 当前master总的核数*/ } } object RDD_Instance_Memory1 { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // val rdd:RDD[Int] = sc.parallelize( // List(1, 2, 3, 4) // ) val rdd = sc.makeRDD( List(1, 2, 3, 4) ) println(rdd) } def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd: RDD[Int] = sc.makeRDD(List(1,3,4,5,6)) /*Return an array that contains all of the elements in this RDD. * collect中可以传入一个偏函数*/ rdd.collect().foreach(println) } } object RDD_Instance_File { def main1(args: Array[String]): Unit = { // TODO 从文件中创建RDD // textFile方法可以通过路径获取数据 所以可以将文件作为数据处理的 数据源 // textFile的路径可以是相对路径 也可以是绝对路径 也可以是HDFS路径 // textFile的路径不仅可以是文件的路径 也可以是目录的路径 还可以是通配符路径 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // val rdd :RDD[String] = sc.textFile("text*.txt") // TODO 如果想要在读取到文件的内容时直到文件的来源 // 文件路径 文件内容 val rdd: RDD[(String, String)] = sc.wholeTextFiles("text*.txt") // TODO saveAsTextFile方法可以生成分区文件 构建RDD的时候 如果没有指定数据分区的数量 // 那么会使用默认的分区数量 makeRDD方法存在第二个参数 这个参数表示分区数量 (存在默认值) // 分区的好处可以达到负载均衡和并行消费的能力 rdd.saveAsTextFile("output") rdd.collect().foreach(println) } def main(args: Array[String]): Unit = { /*TODO 从文件中创建RDD textFile可以通过路径获取数据 可以将文件作为数据处理的数据源 * textFile可以是相对路径,可以是绝对路径,也可以是HDFS路径 * textFile路径不仅可以是文件的路径 也可以是目录的路径 还可以是通配符的路径*/ val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc: SparkContext = new SparkContext(conf) /* * This method is identical to `parallelize`. * @param seq Scala collection to distribute * @param numSlices number of partitions to divide the collection into*/ sc.makeRDD(List(1,3,4,5)) sc.parallelize(List(12,3,4,5)) /*TODO 如果想要在读取到文件的内容时知道文件的来源*/ val rdd: RDD[(String, String)] = sc.wholeTextFiles("text*.txt") /*TODO saveAsTextFile 方法可以生成分区文件 构建RDD的时候 如果没有指定数据分区的数量 * 那么会使用默认的分区数量 makeRDD方法存在第二个参数 这个参数表示分区数量 存在默认值 * 分区的好处是可以达到负载均衡和并行消费的能力*/ rdd.saveAsTextFile("output_1") rdd.collect().foreach(println) } } object RDD_Instance_File1 { def main1(args: Array[String]): Unit = { // val conf = new SparkConf().setMaster("local").setAppName("RDD") // val sc = new SparkContext(conf) // val rdd = sc.wholeTextFiles("text.txt") // rdd.saveAsTextFile("output1") // rdd.collect.foreach(println) val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD( List(1, 2, 3, 4, 5), 3 //3 个分区 // 1 , 2 3 , 4 5 ) // TODO 回忆 kafka 生产者分区策略 如果指定了分区号 直接将该值作为分区值 // 没有指明分区但是指明了key 将key的hash值 和topic的分区数取模得到分区值 // 没有指明分区也没有指明key 默认采用粘性分区 // TODO RDD的分区 rdd.saveAsTextFile("output5") println(rdd) rdd.foreach(println) } def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3) /*TODO kafka生产者分区分配策略 如果指定了分区号 直接将该值作为分区值*/ /*没有指明分区但是指明了key 按key的hash值 和topic的分区数量取模得到分区值 * 没有指明分区也没有指明key 默认采用粘性分区*/ rdd.saveAsTextFile("output_5") rdd.foreach(println) } } object RDD_TextFile { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) // TODO 从文件中创建RDD // textFile方法可以在读取文件的时候 设定分区 // 设定分区的时候 应该传递第二个参数 如果不设定 存在默认值 // 第二个参数表示的是最小分区数 所以最终的分区数量可以大于这个值的 // TODO spark 读取文件底层其实就是hadoop读取文件 // spark分区数量其实就来自于 hadoop读取文件的切片 // numSplits 想要的切片数量2 totalSize 文件总大小 20 goalSize => // 预计每个分区的字节大小 //totalSize/numSplits = 10 每个分区放10个字节 // 一共20个字节 需要两个分区 每个分区10个字节 真tm的对 // 如果有剩余的自己 要判断剩余的字节和每个分区的字节数相比,如果大于1.1则 // 再增加一个分区 否则不增加 val rdd = sc.textFile("text.txt", 3) //如果这里是3 字节数totalSize是20 // 每个分区放6个字节 一共20个字节 3个分区 剩2个字节 2/6>0.1 4个分区 // TODO 数据是怎样分配到不同的分区的 // TODO 分区数据的处理也是由Hadoop决定的 // TODO hadoop 在计算分区时和处理数据的逻辑是不一样 处理数据按行读 分区则是按字节放 // TODO Spark读取文件数据底层使用的就是Hadoop读取的 所以读取规则用的是hadoop /* hadoop读取数据是按行读的。不是按字节读的 * hadoop读取数据是按偏移量读取的 不会重复读取相同的偏移量 * 数据 偏移量 * 1@@ 012 * 2@@ 345 * 3 6 * 计算读取偏移量 3个分区 7个字节 * [0,3] 12 * [3,6] 3 * [6,7] 空 * 数据是怎么读到各个分区的 计算分区读取的偏移量 不重复读偏移量 */ rdd.saveAsTextFile("output4") // TODO 早起回顾 // Driver =>Task (调度) =>exector(执行) 如果将数据拉取到其他的节点进行计算 称之为移动数据 // 将任务发送给不同的节点进行计算 称之为移动计算 // TODO 数据和计算 在一起 怎么放置在一起 这个操作涉及到一个本地化的概念 // 数据和计算在同一个进程中 进程本地化 在同一个节点中 节点本地化 不同的任意节点 任意 // 数据和计算在同一机架 机架本地化 // TODO RDD 执行原理 计算资源 和计算模型 Spark on Yarn // } } object RDD_Instance_Dist { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // Textfile()文件的路径可以是绝对路径 可以是相对路径 // 绝对路径:不可改变的路径 // 相对路径:可以改变的路径 相对当前的路径 /* 网络 网络协议 ip地址 端口号 * 本机 file:/// * 默认情况下 相对路径的基准路径为IDEA项目的根路径 不是模块的根路径 * textFile 读取的文件可以是文件 也可以是目录 * 可以用正则表达式 匹配文件名读取文件 * 如果想要获取文件内容是来自于哪一个文件 需要采用方法 wholeTextFiles*/ // val rdd = sc.textFile("text.txt") 这个方法第一个参数表示文件路径 第二个参数表示最小分区数 // 参数存在默认值 是不需要传递的 默认值 min(defaultParallelism,2) // spark的文件操作自己是没有的 底层采用的是hadoop的文件操作 // totalSize:文件总大小 总字节 goalSize:每个分区期望的字节数 // 分区数量 = totalSize / goalSize /* 阈值1.1 剩余字节数/ 切片大小 也就是每个分区的字节数 如果大于 0.1 会增加一个分区/切片 * TODO Spark 数据是怎么分配到分区的 * hadoop 计算分区和读取分区数据方式不一样 * 计算分区是按照字节来计算的 读取分区数据不是按照字节 按照行来读取的 * 读取数据的时候 按照偏移量来计算 相同的偏移量不能重复读取 * 每个分区的偏移量 * 1@@ 012 * 2@@ 345 * 3 6 * [0-3] 12 * [3-6] 3 * [6-7] 空 * RDD的弹性可以体现在分区的变化 默认情况下 分区数据是不均衡的 可以在任务的执行过程中 改变分区 * 让数据更加均衡 * * */ val rdd = sc.wholeTextFiles("text.txt") rdd.saveAsTextFile("output11") /*TODO RDD 转换算子 * 从认知心理学的角度来理解 解决问题的方式其实就是 改变问题的状态 * 问题(初始状态) 问题(审批中) 问题(解决) * 这里的Operator翻译过来就是【算子】 Operator可以理解为 操作 方法 功能 * 算子 : 可以理解为方法 * 取名为算子的原因 : 就是为了和方法区分开 尤其是scala集合的方法 * TODO RDD 的方法称之为 算子 scala集合的方法就是方法/函数 * 转换: 将A 变成B(映射) * 所谓的转化算子 其实就是转换方法 => 通过方法 将旧的RDD转换成B(新的RDD) * 旧的RDD = 算子 > 新的RDD 目的: 拓展功能 将不同的RDD 功能组合在一起 * RDD 使用的基本方式 * * RDD 转换算子 :value类型 双value类型 key-value类型 */ } } object RDD_oper_transform { def main1(args: Array[String]): Unit = { // TODO 算子 - 转换算子 /**/ val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO RDD转换后 分区的变化 :默认执行算子后 1 产生的新的RDD的分区数量不变 // 2 数据处理完后 所在分区不变 val rdd = sc.makeRDD(List(1, 2, 3, 4)) // TODO map 转换映射 // map算子 可以将数据集中的每一条数据转换 映射后 返回新的数据 形成新的数据集合模型 // 旧的RDD =算子 map > 新的RDD 新的RDD包含新的额RDD的功能 将旧的RDD 转换成新的RDD // 并不意味着 转换成新的RDD val newrdd = rdd.map(_ * 2) newrdd.saveAsTextFile("outputnew") newrdd.collect().foreach(println) sc.stop() } def main2(args: Array[String]): Unit = { /*TODO 转换算子 * RDD转换后,分区的变化,默认执行算子后 1 产生新的RDD的分区数量不变 2 数据处理完后 所在的分区不变*/ val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4)) val newRDD: RDD[Int] = rdd.map(_*3) newRDD.saveAsTextFile("output_new") newRDD.collect().foreach(println) sc.stop() } def main(args: Array[String]): Unit = { // TODO 并行的概念 数据的执行顺序 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4)) // TODO 因为spark的计算是并行分布式计算 那么不同分区的数据没有顺序 // 但是同一个分区的数据是有序的 分区间是无序的 // TODO 如果conf的配置 是local 单核的 map算子的执行就是串行的 // 如果是local* 就是并行的 rdd.map( num => { println("num" + num) num * 2 } ) sc.stop() } } object Spark_Exercise_Transform { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 - 转换 -map // 从服务器日志Apache.log中获取用户请求URL资源路径 val lineRDD = sc.textFile("Apache.log") //flatMap() => 扁平化 => 取得的是原来数据拆分的整体 但是我们只要URL //所以我们这里用的是 map map可以格式转化后取得部分数据 val urlRDD = lineRDD.map( line => { val arrayline = line.split(" ") arrayline(6) } ) println(urlRDD) } def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) // 算子-转换 val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5)) val rdd1: RDD[Int] = rdd.mapPartitions( list => { println("*********") list.map(_ * 2) } ) rdd1.collect().foreach(println) sc.stop() } def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 - 转换 val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) val rdd1 = rdd.mapPartitions(//拿到一个分区的所有的数据 list => {//每一个分区都会走一遍该逻辑 println("****************") //这个方法会走两遍 因为有两个分区 list.map(_ * 2) } ) rdd1.collect().foreach(println) sc.stop() // TODO 装饰者设计模式:其实就是一个完整的逻辑被切分成不同的组合模块 // 数据集中的每一条数据必须全部处理完毕 才会执行下一条逻辑 } } object RDD_oper_Transform { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // 可以以分区为单位进行数据的处理 处理的逻辑会调用分区数量的次数 // 既然以分区为单位 所以处理的数据 就是分区的数据集合 处理后 返回的也应改是数据集合 // TODO mapPartitions的性能比map会高一些 // 主要体现在资源的损耗上 mappartitions的资源损耗以分区为单位 map以单个数据为单位 // 从数据处理的角度 两者区别 不大 // mappartitions在处理数据的时候 会依托当前的内存 所以内存不足的情况 不推荐使用 // TODO map在处理完数据后 数据会被释放 所以内存占用不会很多 推荐使用 // mappartitions从使用的角度来讲 传递一个迭代器 返回一个迭代器 所以无需考虑数据的个数 // map从使用的角度来讲 传递一个数据 返回一个数据 数据不能丢失 不能变多也不能变少 val rdd1 = rdd.mapPartitions( iter => { println("********") iter } ) rdd1.collect() sc.stop() } def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2) val rdd1: RDD[Int] = rdd.mapPartitions( iter => { println("********") iter //传递一个迭代器 返回一个迭代器 迭代器中数据的个数 } ) rdd1.collect().foreach(println) sc.stop() } } object Test_Oper_Max_partition { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO 获取每个分区的最大值 // map只考虑数据本身 而不考虑所在的分区 // TODO mappartition要求返回的数据的迭代器 而不是某个值 // val rdd1 = rdd.mapPartitions( // iter =>{ // List(iter.max).iterator // } // ) // TODO 获取指定分区的最大值 // TODO Spark提供了一个算子 可以获取分区 数据的同时 获取分区编号 // val rdd1 = rdd.mapPartitionsWithIndex( (index, iter) => { if (index == 1) { List(iter.max).iterator } else { Nil.iterator } } ) rdd1.collect().foreach(println) sc.stop() } def main4(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2) val rdd1: RDD[Int] = rdd.mapPartitionsWithIndex( (index, iter) => { if (index == 1) {//对每一个分区的数据进行处理 以分区为单位 iter } else { Nil.iterator } } ) rdd1.collect().foreach(println) sc.stop() } def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - 扁平映射 val rdd = sc.makeRDD(List(List(1, 2), List(3, 4))) // TODO 获取每个分区的最大值 // TODO flatmap :扁平化 将整体拆分成个体数据 将1个变成N个 // flatmap 参数要求 :传递一个值 返回一个可迭代的集合 // 概念(原理)和应用不一样 val newRDD = rdd.flatMap( list => list ) newRDD.collect().foreach(println) sc.stop() } def main6(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(List(1,2),List(3,4))) rdd.flatMap( list => list ) rdd.collect().foreach(println) sc.stop() } def main3(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - 扁平映射 val rdd = sc.makeRDD(List(List(1, 2), 5, List(3, 4))) // TODO 获取每个分区的最大值 // TODO flatmap :扁平化 将整体拆分成个体数据 将1个变成N个 // flatmap 参数要求 :传递一个值 返回一个可迭代的集合 // 概念(原理)和应用不一样 // val newRDD = rdd.flatMap( // case i : List[_] =>{ // i // } // case other : Int => { // List(other) // } // ) // newRDD.collect().foreach(println) sc.stop() } def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - glom val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO glom 算子可以将一个分区的数据组合成一个整体使用 可以调用集合的很多功能 // TODO 问题在于处理的时候 会将一个分区数据全部加载到内存 形成集合 那么内存可能不足 /* Return an RDD created by coalescing all elements within each partition into an array.*/ val rdd1 = rdd.glom()//返回元素是array的rdd 一个分区的所有元素组成了一个array val max = rdd1.map( array => array.max ) println(max.sum()) } def main7(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - 数据的分区 // 一条数据的处理 肯定要落盘 Shuffle的操作会将整个数据的流程一分为二 分为两个阶段 // 一个阶段将数据 用于将数据写入磁盘 全部写完后 另一个阶段 用于读取磁盘中的数据 // shuffle会将数据打乱重新组合 可能会导致数据的不均衡 // 如果数据不均衡 会导致资源的浪费 可以尝试改变分区解决 // val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO 分组将数据根据指定的规则 增加标记 相同标记的数据放在一起 // 分组后 数据类型为 kv元类型 其中k是分组的标记 v就是相同标记的可迭代集合 val rddgroup = rdd.groupBy(_ % 2) rddgroup.collect().foreach(println) sc.stop() } def main8(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - GroupBy val rdd = sc.makeRDD(List("hello", "nihao"), 2) val rddgroup = rdd.groupBy(_.charAt(0))//k 为分组的标记 v 为 iterable可迭代的集合 rddgroup.collect().foreach(println) } def main9(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 -转换算子 - GroupBy // groupBy算子 根据函数计算结果进行分组 // groupBy算子执行结果 为KV数据类型 k为分组的表示 v为同一个组的数据可迭代集合 // TODO 默认情况下 数据处理后 所在的分区不会发生改变 // spark要求 一个组的数据必须在一个分区中 // 同一分区的数据被重新分配 称为Shuffle // TODO shuffle会将计算过程一分为二 形成两个阶段 一个阶段用于写数据 一个阶段用于读数据 // 写数据的阶段如果没有完成 读数据的阶段是不能执行的 // shuffle操作可以更改分区 val rdd = sc.textFile("Apache.log") val timeToOne = rdd.map( line => { val datas = line.split(" ") val time = datas(3) (time.split(":")(1), 1) } ) val groupRDD = timeToOne.groupBy(_._1) val result = groupRDD.mapValues(_.size) result.collect().foreach(println) sc.stop() } } object Little_Test { def main1(args: Array[String]): Unit = { // TODO 算子 - 转换 根据单词的首个字母进行分组 val conf = new SparkConf().setMaster("local").setAppName("RDD") conf.set("spark.local.dir", "e:/test") val sc = new SparkContext(conf) val rdd = sc.makeRDD( List("Hello", "hive", "hbase", "Hadoop") ) val rddgroup = rdd.groupBy(_.substring(0, 1).toUpperCase()) //不区分大小写 rddgroup.collect().foreach(println) sc.stop() } // TODO 从服务器日志中统计Apache.log中获取每个时间段访问量 def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") conf.set("spark.local.dir", "e:/test") val sc = new SparkContext(conf) val rddlines = sc.textFile("Apache.log") val groupRDD = rddlines.map( line => { val arrayline = line.split(" ") val time = arrayline(3) val timearray = time.split(":") (timearray(1), 1) } ).groupBy(_._1) //TODO groupBy这个算子 可以实现Wordcount (1/10) v是可迭代的集合 val timeCount = groupRDD.mapValues(_.size) timeCount.collect().foreach(println) } } object filter_Test { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 转换 filter 过滤 val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6)) // TODO filter可以按照指定的规则对每一条数据进行过滤筛选 根据指定的规则进行筛选过滤 符合规则的 // TODO 数据保留 不符合规则的数据丢弃 当数据进行筛选过滤后 分区默认不变 但分区的数据可能不均衡 // TODO 出现数据倾斜 比如两个分区 个1000条数据 过滤后一个分区就剩1条 另一个分区剩999条 // 数据处理结果为true 表示数据保留 相反 如果为false 表示数据丢弃 /*Return a new RDD containing only the elements that satisfy a predicate*/ val filterRDD = rdd.filter( num => { num % 2 == 1 //过滤出奇数 ==1返回true 保留 } ) filterRDD.collect().foreach(println) } } object little_Test_filter { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 算子 filter 从服务器日志数据Apache.log中获取2015年5月17日的请求路径 val rddline = sc.textFile("Apache.log") val filterLine = rddline.filter( line => { val lineArray = line.split(" ") val time = lineArray(3) time.startsWith("17/05/2015") //字符串以XXX开头 返回的是true则保留 否则丢弃 } ) filterLine.collect().foreach(println) //过滤的结果是完整的行 val r = filterLine.map( line => { val lineArray = line.split(" ") lineArray(6) } ) r.collect().foreach(println) // TODO 解决数据倾斜? 扩容 hashmap 极限情况下放11条数据形成红黑二叉树 // 放第九个的时候 16扩充32 放第十个的时候 32 扩充到64 第11个的时候调整结构形成红黑二叉树 // 数据抽样 推算 } } object filter_Test_1 { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO filter filter 算子可以将指定的数据进行过滤 满足的数据保留 不满足的丢弃 // val lineRDD = sc.textFile("Apache.log") val rdd1 = lineRDD.filter( line => { val arrayLine = line.split(" ") val time = arrayLine(3) time.startsWith("17/05/2015") } ).map( line => { val array = line.split(" ") array(6) } ) rdd1.collect().foreach(println) sc.stop() } def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(1 to 10) //range // TODO 从数据集中抽取一部分数据 数据抽样 hive中的tablesample /*抽取的时候 方式有两种 : * 1 不放回抽样 不会出现重复数据 * 2 放回抽样 会出现重复数据 sample 算子的第一个参数表示抽样是否 放回 true 放回 false 不放回 如果是不放回抽样: 第二个参数 表示每一条数据被抽取的概率 如果是放回抽样 :第二个参数表示 每一条数据期望别抽取的次数 第三个参数 : 表示数据抽取时的随机数种子 seed: 如果种子相同 抽取种子的随机数是相同的 意味着抽取的数据是一样的 随机数算法*/ val sampleRDD = rdd.sample(false, 0.5, 2) rdd.sample(false,0.5,2) sampleRDD.collect().foreach(println) sc.stop() } def main3(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 1, 2)) // TODO distinct 去重 scala 集合的去重采用HashSet 但是这是单点集合操作 // spark RDD的去重不能使用单点 内存不够用 /* 分布式去重 : case _ => map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)*/ /*Return a new RDD containing the distinct elements in this RDD*/ val rdd1 = rdd.distinct() //distinct 存在shuffle操作 可以改变分区 不传 默认分区数量相同 rdd1.collect().foreach(println) sc.stop() } def main4(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3) //TODO 在某些场合 需要减少分区的数量 采用特殊的算子 coalesce(合并联合)可以减少分区 目的是为了减少分区 // 减少资源的浪费 coalesce合并分区的时候 合并的是临近的两个分区 而不是数据量少的分区 // 可能会导致数据更加不均衡 多的和多的合并了 // coalesce算子的第二个参数 表示是否使用shuffle 默认为false 改成true // 可以使数据更加均衡 /*TODO coalesce 算子 第一个参数表示修改分区的数量 coalesce 默认设计不采用shuffle 数据改变 * 分区后 不会打乱重新组合 如果不采用shuffle coalesce 算子是不能够增大分区的 数据不会打乱 * 如果有shuffle 分区是可以扩大的 shuffle 可以打乱数据 但是不一定 让数据均衡 * TODO spark 提供了一个专门用于改变分区的算子 coalesce 专门用于减少分区 * TODO 如果要增加分区 用: repartition 底层用的就是coalesce(numPartitions, shuffle = true)*/ // val rdd1 = rdd.coalesce(2,true) /*Return a new RDD that has exactly numPartitions partitions. * * Can increase or decrease the level of parallelism in this RDD. Internally, this uses * a shuffle to redistribute data. * * If you are decreasing the number of partitions in this RDD, consider using `coalesce`, * which can avoid performing a shuffle.*/ val rdd1 = rdd.repartition(6) rdd1.saveAsTextFile("outputcoal") sc.stop() } def main5(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6)) // TODO sortBy 将数据按照指定的规则排序 sortBy算子默认为升序 sortBy是有shuffle的 可以 // 重新改变分区 // 如果想降序 传递第二个参数 false 降序 // 数据本身 排序的维度 /*Return this RDD sorted by the given key function.*/ val newrdd = rdd.sortBy(num => num, false) //把XXX当成什么来排 newrdd.collect().foreach(println) } def main6(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2) val rdd1 = sc.makeRDD(List(7, 8, 9), 2) // TODO 交集 两个数据集的泛型一致 println(rdd.intersection(rdd1).collect().mkString(",")) // TODO 并集 泛型也要保持一致 println(rdd.union(rdd1).collect().mkString(",")) // TODO 差集 泛型也要保持一致 println(rdd.subtract(rdd1).collect().mkString(",")) // TODO 拉链 要求 分区数相同 分区内的元素个数相同 泛型T 和U 可以相等也可以不相等 /*Can only zip RDDs with same number of elements in each partition * */ /*Zips this RDD with another one, returning key-value pairs with the first element in each RDD, * second element in each RDD, etc. Assumes that the two RDDs have the *same number of * partitions* and the *same number of elements in each partition* (e.g. one was made through * a map on the other).*/ println(rdd.zip(rdd1).collect().mkString(",")) } def main7(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("b", 2))) /* Extra functions available on RDDs of (key, value) pairs through an implicit conversion.*/ // TODO partitionBy 所有kv类型数据操作的方法 在RDD中不存在 kv操作都是在PairRDDFunction类 // 中完成的 编译出错 二次编译 隐式转换 RDD 成 PairRDDFunction // TODO partitionBy 算子用于将数据进行重分区 coalesce用于缩减 或扩大分区 数据和分区的关系 // 考虑 partitionBy 主要目的是将数据改变分区 需要传递一个分区器 // TODO spark 默认提供了三个 分区器 Hash/Range/Python/ partitioner 常用的是Hash 和Range //* Return a copy of the RDD partitioned using the specified partitioner. val rdd1 = rdd.partitionBy(new HashPartitioner(2)) rdd1.saveAsTextFile("hashoutput") sc.stop() } def main8(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9))) // TODO reduceByKey 将相同的key的value聚合在一起实现统计操作 // reduceByKey 可以将分区和聚合 合二为一 实现 WordCount (2 / 10) val rdd1 = rdd.reduceByKey(_ + _) //相同的key v聚合 rdd1.collect().foreach(println) sc.stop() } def main9(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9), ("b", 7))) // TODO 算子 - 转换 - reduceByKey // reduceByKey算子的作用 是将相同的key的value 分在一个组中 然后进行reduce操作 // TODO 将reduceByKey将分组和聚合合二为一 可以实现WordCount ( 2 / 10) val wordCount = rdd.reduceByKey(_ + _) /*TODO Collect方法分析: * * @note This method should only be used if the resulting array is expected to be small, as * all the data is loaded into the driver's memory. * TODO /** * Return an RDD that contains all matching values by applying `f`. */ def collect[U: ClassTag](f: PartialFunction[T, U]): RDD[U] = withScope { val cleanF = sc.clean(f) filter(cleanF.isDefinedAt).map(cleanF) } TODO Collect方法可以传入一个偏函数 */ wordCount.collect().foreach(println) //(a,10) (b,9) sc.stop() } def main10(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9))) // TODO groupBy算子可以采用任意的数据作为分组 的标记 // groupByKey算子可以采用数据的key 作为分组的标记 返回结果的类型为元组类型 // TODO 如何提高shuffle 的性能 1 花钱 提升硬件性能 2 增加磁盘缓冲区 3 减少磁盘IO // groupByKey在shuffle的时候是无法减少数据的 // reduceByKey根据相同的key 聚合v 聚合后的value是可迭代的集合 // 在shuffle之前提前聚合(combine):预聚合 // 可以提高性能 性能优于groupByKey /*Group the values for each key in the RDD into a single sequence. Hash-partitions the * resulting RDD with the existing partitioner/parallelism level. The ordering of elements * within each group is not guaranteed, and may even differ each time the resulting RDD is * evaluated. * * @note This operation may be very expensive. If you are grouping in order to perform an * aggregation (such as a sum or average) over each key, using `PairRDDFunctions.aggregateByKey` * or `PairRDDFunctions.reduceByKey` will provide much better performance.*/ val rddGroup = rdd.groupByKey() //TODO groupByKey 也可以做WordCount ( 3 /10 ) val wordCountRDD = rddGroup.mapValues(_.sum) //a 10 b 2 val wordCountSize = rddGroup.mapValues(_.size) // a 2 b 1 wordCountSize.collect().foreach(println) wordCountRDD.collect().foreach(println) // TODO groupByKey 和groupBy 的区别 // groupby不需要考虑数据类型 groupByKey 必须保证数据 kv类型 // groupBy按照指定的规则分组 groupByKey必须根据key对value进行分组 // 返回结果 : groupByKey 返回结果是 (String, Iterable[Int]) v的集合 // groupBy 返回结果 (String,Iterable[(String,Int)]) // groupBy的底层还是groupByKey /*TODO reduceByKey分完组 有聚合的能力 而groupByKey先根据key进行分组产生一个RDD * 然后聚合 又产生一个RDD reduceByKey在落盘之前可以减少数据量 相当于map阶段进行了combiner * TODO reduceByKey 可以在Shuffle之前 对分区内的数据进行预聚合 称之为combine * TODO 面试问题 reduceByKey和groupByKey的区别 * 从shuffle的角度讲 都存在shuffle操作 但是reduceByKey可以在shuffle 前对分区内相同的key * 进行数据的聚合 combine 功能 这样会减少落盘的数据量 * groupByKey只是进行分组 不存在数据量减少的问题 reduceByKey 性能比较高 * 从功能的角度讲 : reduceByKey 其实包含分组和聚合的功能 。 groupByKey只能分组 不能聚合 * 所以在分组聚合的场合下 推荐使用 reduceByKey 如果仅仅是分组不需要聚合 那么还是使用groupByKey * */ } def main11(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2) //TODO 取出每个分区内相同key的最大值 然后分区进行相加 /*【(a,1),(a,2),(b,3)] => [(a,2),(b,3)] * [(b,4),(b,5),(a,6)] => [(b,5),(a,6)] =====>[(a,8),(b,8)] * TODO aggregateByKey这个算子 存在函数的柯里化 * 第一个参数列表中有一个参数 * 参数为零值 表示计算初始值 zero z 用于数据分区内计算用的 如_+_ 单独一个key的数据无法进行计算 * 第二个参数列表中有两个参数 * 第一个参数表示 分区内计算规则 第二个参数表示分区间计算规则 */ val rdd1 = rdd.aggregateByKey(5)(//相同key的初始值为0 (x, y) => { math.max(x, y) //分区内找出相同key的最大值 }, (x, y) => { //分区间 把相同key的最大值相加 由于要将相同的key的数据从不同的分区放到一个分区 // 所以分区间的操作时有 shuffle的 x + y } ) // TODO aggregateByKey也可以实现WordCount ( 4 / 10 ) val rdd2 = rdd.aggregateByKey(0)(_ + _, _ + _) //通过相同的key聚合 分区内的计算逻辑 // 和分区间的计算逻辑都是key相同的两个数据 _+_ ,就可以实现WordCount // TODO 如果aggregateByKey算子的分区内计算逻辑和分区间计算逻辑相同 那么可以使用 foldByKey // 算子简化 foldByKey()实现WordCount ( 5 / 10) val rdd3 = rdd.foldByKey(0)(_ + _) rdd1.collect().foreach(println) rdd3.collect().foreach(println) sc.stop() } def main12(args: Array[String]): Unit = { // TODO 求每个key的平均值 将数据List(("a", 88), ("b", 95), ("a", 91), ("b", 93), // ("a", 95), ("b", 98))求每个key的平均值 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2) // TODO combineByKey有三个参数 第一个参数表示 当第一个数据数据不符合我们的规则时 // 用于转换的操作 /*第二个参数表示 表示分区内计算规则 第三个参数表示 表示分区间计算规则 如果将数据格式变成 ("a", 1) => ("a", (1,1)) 就可以做平均值的计算了 */ val rdd2 = rdd.combineByKey( num => (num, 1), (x: (Int, Int), y) => { //分区内 是一个Tuple和一个value聚合操作 Tuple的格式(次数,个数1) // 分区内的计算结果都是 (key,(次数,个数)) (x._1 + y, x._2 + 1) //相同key的次数相加 个数加一 }, // 分区间相同的key放一块 两个Tuple 分区间计算 (x: (Int, Int), y: (Int, Int)) => { //分区间的计算结果相加 (x._1 + y._1, x._2 + y._2) //相同key的次数相加 和 个数相加 } ) rdd2.collect().foreach(println) sc.stop() } def main13(args: Array[String]): Unit = { // TODO combineByKey()的WordCount实现 ( 6 / 10) val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2) val rdd1 = rdd.combineByKey( num => num, (x: Int, y) => { //分区内 相同key 相加就行 ("a",8)("b",8) x + y }, (x: Int, y: Int) => { //分区间 相同的key 放到一个分区 相同的key相加 ("a",xxx) x + y } ) rdd1.collect().foreach(println) sc.stop() } def main14(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2) // TODO 不同方式实现wordCount 以 它们的区别 rdd.reduceByKey(_ + _) /*TODO reduceByKey(func) combineByKeyWithClassTag[V] ((v: V) => v, 第一个key的v不做处理直接返回 func, 分区内的计算规则 func, 分区间的计算规则 分区内和分区间计算规则相同 partitioner) func就是我们传的 * 聚合函数 _+_ */ rdd.aggregateByKey(0)(_ + _, _ + _) /*TODO aggregateByKey(zeroValue)(seqOp,combOp) combineByKeyWithClassTag[U] ((v: V) => cleanedSeqOp(createZero(), v), 分区内的第一个key的v和初始值 计算 cleanedSeqOp, 分区内的计算规则 combOp, 分区间的计算规则 partitioner) */ rdd.foldByKey(0)(_ + _) /*TODO foldByKey(zeroValue)(func) combineByKeyWithClassTag[V] => ((v: V)cleanedFunc(createZero(), v), 第一个key的v和初始值计算 cleanedFunc, 分区内的计算规则 cleanedFunc, 分区间计算规则 partitioner)*/ rdd.combineByKey( num => num, (x: Int, y) => { x + y }, (x: Int, y: Int) => { x + y } ) // /*TODO combineByKey(createCombiner,mergeValue, mergeCombiners) // combineByKeyWithClassTag( // createCombiner, 分区内的第一个key的 value的 转换 // mergeValue, 分区内的计算规则 // mergeCombiners, 分区间的计算规则 // defaultPartitioner(self))*/ // TODO mapSideCombine: Boolean = true, 上面几个方法的map端默认都是开启预聚合功能的 // 但是groupByKey没有 map端不支持预聚合功能 默认是hash分区器 rdd.groupByKey() /*combineByKeyWithClassTag[CompactBuffer[V]]( createCombiner, mergeValue, mergeCombiners, partitioner,TODO mapSideCombine = false) bufs.asInstanceOf[RDD[(K, Iterable[V])]]*/ } def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2) // TODO sortByKey() 对key进行排序 val rdd1 = rdd.sortByKey(false) // 对key进行降序排序 rdd1.collect().foreach(println) sc.stop() } } // TODO 如果传入的是自定义的对象进行比较 自定义的类需要实现 特质Ordered 并重写compare方法 class UserOrder extends Ordered[UserOrder] { override def compare(that: UserOrder): Int = 1 } object Join_Test { def main1(args: Array[String]): Unit = { // TODO Join 两个数据集的操作 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6))) val rdd1 = sc.makeRDD(List(("a", 11), ("b", 11))) /*数据库设计的三范式 不能有间接依赖 * user => id, name ,deptId * dept => id ,deptname*/ // TODO spark中Join操作 主要针对 两个数据集中相同 key的数据连接 Join操作可能会产生笛卡尔积 // 可能会出现shuffle 数据落盘 性能会比较差 所以如果可以替代Join 不推荐使用Join // TODO 主表和从表 主表中的数据一定都有 从表中的数据符合条件的才有 主表在join左边就叫 左连接 // 主表 join 在右边 就叫 右连接 val rdd3 = rdd.join(rdd1) // 右边的rdd中的数据可能有也可能没有 用Option类型 some none val rdd4 = rdd.leftOuterJoin(rdd1) //左连接 val rdd5 = rdd.rightOuterJoin(rdd1) //右连接 val rdd6 = rdd.fullOuterJoin(rdd1) val rdd7 = rdd.cogroup(rdd1) //先分组 再连接 最多可以将4个rdd组合成一个 connect + group 分组 + 连接 // rdd3.collect().foreach(println) // rdd4.collect().foreach(println) // rdd5.collect().foreach(println) // rdd6.collect().foreach(println) rdd7.collect().foreach(println) sc.stop() } def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 统计出每一个省份 每个广告 被点击数量排行的Top3 // TODO 1 读取数据文件 获取原始数据 val rddlines = sc.textFile("agent.log") // TODO 2 将原始数据进行结构的转换 line => ((省份,广告),1) val wordToOne = rddlines.map( line => { val datas = line.split(" ") ((datas(1), datas(4)), 1) } ) // TODO 3 将转换结构后的进行统计 // ((省份,广告),1) => ((省份,广告),sum) val wordToSum = wordToOne.reduceByKey(_ + _) // TODO 4 将统计的结果进行结构的转换 将省份独立出来 // ((省份,广告),sum) => (省份,(广告,sum)) val wordChange = wordToSum.map { case ((province, adv), sum) => { //模式匹配 (province, (adv, sum)) } } // TODO 5 将数据按照省份进行分组 // (省份,List((广告,sum),(广告1,sum1),(广告2,sum2))) val groupRDD = wordChange.groupByKey() // TODO 6 将分组后的数据 根据点击数量进行排行 降序 取前三条 打印 val top3 = groupRDD.mapValues( iter => { iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3) } ) top3.collect().foreach(println) sc.stop() } def main3(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 统计出每一个省份 每个广告 被点击数量排行的Top3 // TODO 1 读取数据文件 获取原始数据 val rddlines = sc.textFile("agent.log") // TODO 2 将原始数据进行结构的转换 line => (省份,(广告,1)) val wordToOne = rddlines.map( line => { val datas = line.split(" ") (datas(1), (datas(4), 1)) } ) // TODO 先分组 会有shuffle 就会有落盘 性能就差 所以说groupByKey的方法不如上面的 // reduceByKey 提前预聚合 减少了落盘的数据量 val groupRDD = wordToOne.groupByKey() //返回值类型 (String,Iterable[(String,Int)] val top3 = groupRDD.mapValues( iter => { // TODO 这里内存中的数据量很大 val advSum = iter.groupBy(_._1).mapValues(_.size) advSum.toList.sortBy(_._2)(Ordering.Int.reverse).take(3) } ) top3.collect().foreach(println) sc.stop() } def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 统计出每一个省份 每个广告 被点击数量排行的Top3 // TODO 1 读取数据文件 获取原始数据 val rddlines = sc.textFile("agent.log") // TODO 2 将原始数据进行结构的转换 line => ((省份,广告),1) val wordToOne = rddlines.map( line => { val datas = line.split(" ") ((datas(1), datas(4)), 1) } ) // TODO 3 将转换结构后的进行统计 // ((省份,广告),1) => ((省份,广告),sum) val wordToSum = wordToOne.reduceByKey(_ + _) // TODO 4 将统计的结果进行结构的转换 将省份独立出来 // ((省份,广告),sum) => (省份,(广告,sum)) val wordChange = wordToSum.map { case ((province, adv), sum) => { //模式匹配 (province, (adv, sum)) } } val groupRDD = wordChange.groupByKey() // TODO 将分组后的数据 根据点击量进行排行 降序 // 将排序后的数据取前三 val top3 = groupRDD.aggregateByKey(ArrayBuffer[(String, Int)]())(// key的初始值 // 为buffer (a,buffer(1)) a,buffer(3,1)=> a,buffer(3,2,1) => a,buffer(4,3,2,1)=> 取前三 // a,buffer(4,3,2) (buff, t) => { buff.appendAll(t) //把值(广告,sum)放到buff里面 buff.sortBy(_._2)(Ordering.Int.reverse).take(3) //分区内排序 降序 最后取三个 }, /*TODO 先进行分区内的排序操作 再进行分区间的排序操作 效率要比直接放到内存中高 */ (buffer1, buffer2) => { buffer1.appendAll(buffer2) // a,buffer(4,3,2,7,5,1) => 降序取前三 a,buffer(7,5,4) // 相同的key只会保留三条数据 不会消耗很大的内存 buffer1.sortBy(_._2)(Ordering.Int.reverse).take(3) } ) top3.collect().foreach(println) sc.stop() } } object sample_Test { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(1 to 10) // TODO 抽取数据 数据抽样 1 抽取放回 2 抽取不放回 // TODO Sample 抽取数据的方式 true表示抽取放回 false 表示抽取不放回 // 如果是抽取不放回的场合 参数表示每条数据被抽取的几率 抽取的数据条数不一定 // 如果是抽取放回的场合 参数表示每条数据希望被抽取的次数 // val rdd1 = rdd.sample(false,0.5) // val rdd1 = rdd.sample(true,2) //放回抽样 希望抽取的次数是两次 但是不一定是两次 // fraction 分数 seed 种子 TODO 不放回抽样 第三个参数是随机数种子 随机数不随机 // 所谓的随机数 是随机算法获取的一个数 如果随机数种子固定 以后的随机数通过同样的随机算法计算的 // 结果一样 var rdd1 = rdd.sample(false, 0.5, 1) /* withScope { require(fraction >= 0.0, "Negative fraction value: " + fraction) if (withReplacement) { new PartitionwiseSampledRDD[T, T](this, new PoissonSampler[T](fraction), true, seed) } else { //TODO 泊松分布 new PartitionwiseSampledRDD[T, T](this, new BernoulliSampler[T](fraction), true, seed) } TODO 伯努利分布 正态分布: 正态分布(Normal distribution)又名高斯分布(Gaussiandistribution),若随机变量X服从一个数学期望为μ、方差为σ^2的高斯分布,记为N(μ,σ^2)。其概率密度函数为正态分布的期望值μ决定了其位置,其标准差σ决定了分布的幅度。我们通常所说的标准正态分布是μ = 0,σ = 1的正态分布。 当μ=0,σ=1时,正态分布就成为标准正态分布N(0,1)。概率密度函数为: 正态分布的密度函数的特点是:关于μ对称,并在μ处取最大值,在正(负)无穷远处取值为0,在μ±σ处有拐点,形状呈现中间高两边低,图像是一条位于x轴上方的钟形曲线。 以上摘自:https://blog.csdn.net/zhaozhn5/article/details/78336366 概述:量存在正态分布,比如同一个人测量一件物品长度的误差、比如相同环境下同一种族的身高分布。 泊松分布: 在统计学上,只要某类事件满足三个条件,它就服从"泊松分布"。三个条件分别是:1、事件X的发生是小概率事件。2、事件X的发生是随机而且互相独立的。3、事件X发生的概率相对稳定。 泊松分布的公式如下: P(X=k)=\frac{e^{-\lambda}\lambda^k}{k!} 各个参数的含义:单位时间(或单位面积)内随机事件的平均发生率;k事件X发生的频数;P(X=k)事件X发生k次的概率。 泊松分布与二项分布的关系 当二项分布的n很大而p很小时,泊松分布可作为二项分布的近似,其中λ为np。通常当n≧20,p≦0.05时,就可以用泊松公式近似得计算。事实上,泊松分布也是由二项分布推导而来。 应用实例:http://www.ruanyifeng.com/blog/2013/01/poisson_distribution.html 泊松分布的期望为E(X)=λ,方差D(X)=λ。 伯努利分 以下内容节选自百度百科: 一个非常简单的试验是只有两个可能结果的试验,比如正面或反面,成功或失败,有缺陷或没有缺陷,病人康复或未康复。为方便起见,记这两个可能的结果为0和1,下面的定义就是建立在这类试验基础之上的。 如果随机变量X只取0和1两个值,并且相应的概率为: 则称随机变量X服从参数为p的伯努利分布,若令q=1一p,则X的概率函数可写为: 伯努利分布的期望E(X)=p,D(X)=p(1-p)。 n重伯努利分布的期望E(X)=np,D(X)=np(1-p)。 */ rdd1.collect().foreach(println) } } object partitionRelease { def main(args: Array[String]): Unit = { // TODO 算子 转换 缩减分区 或者 shuffle重新洗牌 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(1 to 10, 3) // TODO 缩减 (合并) 缩减分区其实就是合并部分分区 默认缩减分区不会shuffle打乱数据 // 这里合并的依据并不是数据的多少 而是依据 首选位置 离得近的合并 并不能解决数据倾斜问题 // TODO 进行缩减分区的时候 shuffle 打乱数据 数据会均匀一些 不是绝对均匀 val rdd1 = rdd.coalesce(2) val rdd3 = rdd.coalesce(2, true) rdd.saveAsTextFile("outputCoalesce") rdd1.saveAsTextFile("outputrdd") rdd3.saveAsTextFile("outputShuffle") sc.stop() } } object Operator_Action { def main1(args: Array[String]): Unit = { // TODO main 方法是一个应用程序 或Driver程序 一个应用程序里面可能有多个作业 val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 行动算子 Spark RDD 方法分为两大类 其中一个是转换算子 一个为行动算子 /*TODO 转换算子只是功能逻辑的封装 不会自己执行 行动算子在被调用的时候 会触发 Spark作业 * 的执行 只要调用行动算子就会触发 * TODO collect()方法就是行动算子 rdd.collect() new一个job 行动算子执行时 会构建新的作业 * 一个行动算子对应一个作业 * TODO 回顾总结 全落盘和聚合之后再落盘 明显数据量先聚合 会少很多 reduceByKey的性能会高很多 * 与聚合相关的算子 reduceByKey aggregateByKey foldByKey combineByKey 通过源码解析他们 * 之间的关系 reduceByKey 其实就是分区内和分区间的计算规则相同 第一个参数 key的zerovalue * 初始值不做处理 直接返回 aggregateByKey有个零值 初始值 和第一个值两个值一块做分区内的计算 * 分区内和分区间的规则可能不一样 也可能一样 如果规则一样可以简化成 foldByKey * combineByKey意思是说 如果我们的数据格式不满足要求 通过第一个括号的参数来改变第一个key的value * 的格式 不改也行 这几个算子都有map端的预聚合 map的combine map端会有shuffle 把完整的流程一分 * 为2 写文件称为shufflemap 读文件称之为shufflereduce 又叫上游阶段和下游阶段 * TODO 之前学的转换算子将旧的RDD转换成新的RDD 目的是为了扩展功能 和功能的叠加 通过不同功能 * 组合完成具体的业务 通过装饰者设计模式将不同的功能组合在一块 TODO 类似于IO流中的高级流 包装 * 低级流 真正实现读的功能的还是低级流 而高级流只是起到装饰拓展功能的作用 比如增加缓冲区 将字节流 * 转换成字符流 * TODO 与面试官面试的时候 有意识的将不同的知识点穿插到一块 RDD就是一个数据模型 更适合并行计算 * 重复使用 */ val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) //两个分区 先分区内计算 在分区间计算求和 val i = rdd.reduce(_ + _) // 会返回结果 之前的reduceByKey...会产生新的RDD // TODO 行动算子会得到结果 转换算子会产生新的RDD 算子和方法的区别 分布式和单点 /*TODO reduce()方法的底层: sc.runJob(this, reducePartition, mergeResult) // Get the final result out of our Option, or throw an exception if the RDD was empty jobResult.getOrElse(throw new UnsupportedOperationException("empty collection")) } TODO 此处的reduce和scala中的reduce不同:此处的reduce是算子 而scala中的reduce是方法 算子和方法的区别 :算子 可以有不同的分区 分布式计算 */ println(i) // TODO collect() 行动算子 将数据从Executor端采集到Driver端 // collect()会将数据全部拉取到Driver端的内存中 形成数据集合 可能会导致内存溢出 一般保存到磁盘 // 的分区文件中 val array = rdd.collect() //返回值类型 Array[Int] println(array.mkString(",")) val l = rdd.count() //TODO count()行动算子 计算元素的个数 println(l) // TODO first() scala中集合的方法有 head tail init last val int = rdd.first() //获取元素的第一个值 // TODO take() 取几条数据 val arraytake = rdd.take(3) //返回值类型是 Array[Int] println(arraytake.mkString(",")) // TODO takeOrdered 行动算子 val arrayOrder = rdd.takeOrdered(3) //排完序后 取前三个 println(arrayOrder.mkString(",")) sc.stop() } def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO aggregate 和 aggregateByKey 的区别 // 1 数据格式 2 aggregateByKey 是一个转换算子 所以执行后 会产生新的RDD // 3 aggregate是一个行动算子 执行后会得到结果 // 4 aggregateByKey执行计算的时候 初始值只会参与分区内的计算 // TODO aggregate 在执行计算的时候 初始值 不仅仅会参与分区内的计算 也会参与分区间的计算 /*下面的结果是25 原因: [1,2] [3,4] => [1,2,5] [3,4,5] => [8] [12] =>[5,8,12] * => 25*/ val i = rdd.aggregate(5)(_ + _, _ + _) //和aggregateByKey的区别是这里没有key的概念 println(i) rdd.fold(5)(_ + _) //TODO fold 如果aggregate 分区内的计算方法和分区间的计算方法一样的话 // 可以简写成 fold 这些方法和scala的思想是一样的 只不过spark中的是算子 分布式的 针对的是RDD sc.stop() } def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO countByKey 统计相同的key出现了多少次 可以通过countByKey实现WordCount /*TODO 思路 先扁平化 拆成 ("a",4) => ("a",1)("a",1)("a",1)("a",1) * 把一个完整的对象中的每一个属性拆出来 叫做扁平化 把Tuple中第一层的元素拆分叫做扁平化 * 把单一的对象 拆成多个对象也叫扁平化 TODO countByKey实现WordCount ( 7 / 10) * */ val rdd1 = rdd.map(("a", _)) val stringToLong = rdd1.countByKey() println(stringToLong) //Map(a -> 4) // TODO countByValue()的value不是kv键值对中v的意思 而是kv整体出现的次数 // 集合中的每一个值出现了多少次 /*RDD的算子 在处理数据上分为三大类 : 1 单value类型 双value类型 k-v类型 * TODO countByValue实现WordCount ( 8 / 10) 将数据格式进行转换("a",4) => * "a" ,"a", "a" , "a" * TODO 准备考试 WordCount 百度去查10 种WordCount*/ val tupleToLong = rdd1.countByValue() println(tupleToLong) rdd.saveAsTextFile("outputsave") //保存成分区文件 rdd.saveAsObjectFile("outputObject") //保存成序列化文件 // rdd.saveAsSequenceFile() 保存成序列文件 sc.stop() } } object action_Foreach { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // TODO 行动算子 foreach // collect()是按照分区号码进行采集的 先采集0号分区.... 所以它的采集顺序和输入顺序是一样的 rdd.collect().foreach(println) //将collect返回给Driver端的结果进行foreach println("**************************************************") rdd.foreach(println) //在Executor端进行遍历打印 如果有多个分区 多个executor 打印出的结果 // 分区内有序 分区间无序 sc.stop() } } object Little_Test_2 { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List[Int](), 2) // TODO 算子 - 行动 val user = new User //在Driver的内存中创建了 user对象 rdd.foreach( num => { println(user.age + num) //需要将Driver中创建的user对象通过网络传给两个Executor User需要 // 序列化 // TODO scala 语法 :匿名函数用到了外部的变量 把它包含到内部 称为闭包 // TODO Spark在执行算子的时候 如果算子的内部使用了外部的变量(对象) 那么意味着一定会 // 出现闭包 出现闭包的三种情况: 1 匿名函数一定会有闭包 2 内部函数在外部使用 也会有闭包 // 原因是改变了 函数函数的生命周期 3 把一个函数当做对象使用也会有闭包 // 在这种场景中 需要将Driver端的变量通过网络传输传递给Executor执行 这个操作不用 // 执行也能判断出来 所以可以在执行前 对数据进行序列化校验 // TODO 在foreach执行runJob之前 执行 sc.clean(f) /*private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = { ClosureCleaner.clean(f, checkSerializable) f } TODO 判断是否是闭包 检查是否序列化 称为闭包检测 TODO spark在执行作业之前 需要进行闭包检测功能 从计算的角度 算子以外的代码 都是在Driver端执行 算子里面的代码都是在Executor端执行 那么在scala 的函数式编程中,就会导致算子内经常会用到算子外的数据 这样就形成了闭包的效果 如果使用算子外的数据 无法序列化 就意味着无法传值给Executor端执行 就会发生错误 所以需要在执行任务前 检测闭包内的对象 是否可以进行序列化 这个操作我们称之为闭包检测 2.12版本后闭包编译方式发生了改变 */ } ) sc.stop() } class User extends /*Serializable*/ { val age = 30 } } object Serial_Test { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("RDD") val sc = new SparkContext(conf) // TODO 行动算子 val rdd = sc.makeRDD(List("Hello", "Hive", "Spark", "Scala")) val s = new Search("S") s.filterByQuery(rdd).collect().foreach(println) } /*case*/ class Search(q: String) /*extends Serializable*/ { // 这里额外的方法需要访问 构造方法的一个参数 所以这个参数提升为Search的属性 // q 是属性 属性和类相关 类需要序列化 如果不继承Serializable 用case 也行 // 样例类 是专门为模式匹配声明的类 除了模式匹配能用外 也能当成普通类来用 生成类的同时 // 自动添加了很多功能 其中有一项就是默认实现了可序列化接口 def filterByQuery(rdd: RDD[String]): RDD[String] = { val s = this.q // Driver内 rdd.filter(_.startsWith(s /*this.q*/)) //这样写的话 Executor中用不到q 用到了s s只是普通的 // 字符串 没有涉及到对象 } } class Test(name: String) { //反编译之后为了让方法test能够用到传给构造器Test的name将name // 提升为Test的属性 def test(): Unit = { // 如果去掉这个方法 那么反编译之后 name 就不会别提升为 Test类的属性 println(name) } } } object Kryo_Serial_Test { def main(args: Array[String]): Unit = { // TODO hadoop中的压缩:减少磁盘使用量 减少网络IO Java的序列化是重量级的 序列化后 对象的提交 // 比较大 Spark处于性能的考虑 从2.0开始 支持另外一种序列化 Kyro 序列化机制 速度是Serializable // 10倍 // TODO 当RDD shuffle数据的时候 简单数据类型 数组 字符串类型 已经在Spark内部使用Kryo来序列化 // TODO RDD 依赖 C ---依赖--> B ----依赖----> A C和A有血缘关系 // 相邻的两个RDD称之为依赖关系 多个连续的依赖关系称之为 血缘关系 // File => textFile => flatMap => map => reduceByKey // TODO RDD 一定会保存血缘关系 没有血缘关系 一旦中间某个RDD出现错误 无法重新计算 val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val lines = sc.textFile("text.txt") println("lines的血缘 :" + lines.toDebugString) val arrayline = lines.flatMap(_.split(" ")) println("arrayline的血缘:" + arrayline.toDebugString) val wordToOne = arrayline.map((_, 1)) // TODO 双向的一对多 是多对多 println("wordToOne的依赖 :" + wordToOne.dependencies) println("wordToOne的血缘" + wordToOne.toDebugString) val wordCount = wordToOne.reduceByKey(_ + _) println("wordCount的依赖: " + wordCount.dependencies) println("wordCount的血缘 :" + wordCount.toDebugString) /*wordCount的血缘 :(1) ShuffledRDD[4] at reduceByKey at RDD_Instance_Memory.scala:1233 [] +-(1) MapPartitionsRDD[3] at map at RDD_Instance_Memory.scala:1231 [] | MapPartitionsRDD[2] at flatMap at RDD_Instance_Memory.scala:1229 [] | text.txt MapPartitionsRDD[1] at textFile at RDD_Instance_Memory.scala:1227 [] | text.txt HadoopRDD[0] at textFile at RDD_Instance_Memory.scala:1227 []*/ wordCount.collect().foreach(println) // TODO RDD只支持粗粒度 转换 即在大量记录上执行的单个操作 将创建RDD的一系列血缘保存下来 // 以便恢复丢失的分区 RDD的Lineage会记录RDD的元数据信息和转换行为 当该RDD的部分分区数据丢失 // 时 他可以根据这些信息 来重新运算和恢复丢失的数据分区 sc.stop() // TODO 依赖关系分为两大类 :窄依赖 OneToOneDependency & 宽依赖 ShuffleDependency /*TODO 上游旧的RDD的一个分区的数据 被下游RDD一个分区所独享 称之为窄依赖 * 上游旧的RDD的一个分区的数据 被下游的RDD的多个分区共享 称之为宽依赖 上游中两个分区的数据 * 进入到下游一个分区 被这一个分区独享也称为 窄依赖 * 这里所说的依赖关系:其实就是相邻的两个RDD之间的关系 * TODO 窄依赖 表示上游的RDD的partition最多被下游RDD的一个partition使用 窄依赖 形象的比喻 * 为独生子女 * 宽依赖 表示 同一个父(上游)RDD的partition被多个(下游)RDD的partition依赖 会引起shuffle * 宽依赖 比喻为多生 一般情况下 宽依赖 = ShuffleDependency * * TODO 阶段划分 * // New stage creation may throw an exception if, for example, jobs are run on a // HadoopRDD whose underlying HDFS files have been deleted. finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite) / /创建新阶段可能抛出异常,例如,工作是运行在一个 HadoopRDD的底层HDFS文件已被删除。 TODO val parents = getOrCreateParentStages(rdd, jobId) resultStage有没有上一级的阶段 传入一个RDD 这个RDD 是collect前面的RDD 也就是wordCount getShuffleDependencies(rdd).map { shuffleDep => getOrCreateShuffleMapStage(shuffleDep, firstJobId) }.toList TODO 判断传入的rdd有没有shuffle依赖 wordCountRDD 有shuffle依赖 如果有的话 返回 : HashSet集合 RDD1 RDD2 ; RDD3 shuffleMapStage 写文件操作 ; 读文件操作 TODO 阶段划分 - 源码 阶段划分主要是针对shuffle的操作 第一个阶段是写的阶段 第二个阶段是读的阶段 或者叫第一个是map阶段 第二个是reduce阶段 必须保证前一个阶段执行完毕才能走另外一个阶段 因为shuffle是要落盘的 必须保证上一个阶段所有的数据落盘 TODO 所有的RDD的操作会形成一个完整的阶段:叫做ResultStage 这个阶段可能会因为shuffle 等操作一分为2 val parents = getOrCreateParentStages(rdd, jobId) 上一个阶段 val id = nextStageId.getAndIncrement() val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite) 上一个阶段被包含在了 ResultStage里面 TODO 源码: /** * Returns shuffle dependencies that are immediate parents of the given RDD. **返回洗牌直接给定抽样的父母的依赖关系。 * This function will not return more distant ancestors. *这个函数不会返回更遥远的祖先。 * For example, if C has a shuffle 例如,如果C洗牌 * dependency on B which has a shuffle dependency on A: **依赖B有洗牌依赖: * A <-- B <-- C * calling this function with rdd C will only return the B <-- C dependency. **与抽样C调用这个函数只会返回B < - C的依赖。 * This function is scheduler-visible for the purpose of unit testing. */*这个函数是scheduler-visible为目的的单元测试。 private[scheduler] def getShuffleDependencies( rdd: RDD[_]): HashSet[ShuffleDependency[_, _, _]] = { val parents = new HashSet[ShuffleDependency[_, _, _]] val visited = new HashSet[RDD[_]] val waitingForVisit = new ListBuffer[RDD[_]] waitingForVisit += rdd 把RDD加到集合里面 while (waitingForVisit.nonEmpty) { 判断集合是否为空 val toVisit = waitingForVisit.remove(0) toVisit就是刚刚添加的RDD if (!visited(toVisit)) { 如果这个RDD没有被访问过 visited += toVisit toVisit.dependencies.foreach { case shuffleDep: ShuffleDependency[_, _, _] => parents += shuffleDep 集合parents增加一个依赖 case dependency => waitingForVisit.prepend(dependency.rdd) } } } parents 返回添加完shuffle依赖的集合parents TODO 有一个shuffle map之后 转换成 shufflemapstage Get or create the list of parent stages for a given RDD. The new Stages will be created with * the provided firstJobId. *TODO getShuffleDependencies(rdd).map { shuffleDep => getOrCreateShuffleMapStage(shuffleDep, firstJobId) }.toList } TODO 结论 阶段的数量: shuffle操作的个数 + 1 有几个shuffle依赖就有几个shufflemapstage 然后再加上一个大范围的resultStage*/ } } object Job_Stage { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) // TODO RDD任务划分 RDD任务划分为 // Application :初始化一个SparkContext即生成一个Application // Job :一个行动Action算子生成一个Job // Stage : Stage 等于宽依赖shuffleDependency的个数加 1 // Task :一个Stage 阶段中,最后一个RDD的分区个数就是Task的个数 /*TODO 注意 : Application -> Job -> Stage -> Task 每一层都是1对n的关系 * 源码中 Task的计算 : * /** Submits stage, but first recursively submits any missing parents. */ * private def submitStage(stage: Stage): Unit = { val jobId = activeJobForStage(stage) if (jobId.isDefined) { logDebug(s"submitStage($stage (name=${stage.name};" + s"jobs=${stage.jobIds.toSeq.sorted.mkString(",")}))") if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) { val missing = getMissingParentStages(stage).sortBy(_.id) 获取丢失的上一级阶段 logDebug("missing: " + missing) if (missing.isEmpty) { 如果没有上一级阶段 logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents") submitMissingTasks(stage, jobId.get) 提交丢失的Task 提交上一级的任务用的 } else { 如果有上一级阶段 for (parent <- missing) { submitStage(parent) 上一级阶段挨个遍历 提交 此处用了递归 } waitingStages += stage } } } else { abortStage(stage, "No active job for stage " + stage.id, None) } } TODO 进入 submitMissingTasks(stage, jobId.get) val tasks: Seq[Task[_]] = try { val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array() stage match { case stage: ShuffleMapStage => Stage 模式匹配 stage.pendingPartitions.clear() partitionsToCompute.map { id => 0 1 2 三个分区分别传入 val locs = taskIdToLocations(id) val part = partitions(id) stage.pendingPartitions += id new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber, 三个分区new了三个Task taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId), Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier()) } case stage: ResultStage => partitionsToCompute.map { id => val p: Int = stage.partitions(id) val part = partitions(p) val locs = taskIdToLocations(id) new ResultTask(stage.id, stage.latestInfo.attemptNumber, taskBinary, part, locs, id, properties, serializedTaskMetrics, Option(jobId), Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier()) } } } TODO 进入 // Figure out the indexes of partition ids to compute. val partitionsToCompute: Seq[Int] = stage.findMissingPartitions() TODO 进入 findMissingPartitions /** Returns the sequence of partition ids that are missing (i.e. needs to be computed). */ override def findMissingPartitions(): Seq[Int] = { mapOutputTrackerMaster .findMissingPartitions(shuffleDep.shuffleId) .getOrElse(0 until numPartitions) 0 1 2 0到numPartitions 不包含numPartitions } val numPartitions = rdd.partitions.length TODO 总结 :任务的数量应该是所有的阶段的最后一个RDD的分区数之和 ************************************************************************************ * ResultStage * * * * shffuleMapStage * * RDD * * RDD RDD * * * * * * * * * * * ************************************************************************************ * 上面的例子中总共有6个Task * TODO 一个阶段会根据最后一个RDD的分区来决定Task的数量 一个阶段就对应多个Task * */ } } object Persist_Test { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) sc.setCheckpointDir("cp") // TODO RDD通过Cache 或者Persist方法将前面的计算结果缓存 默认情况下会把数据缓存到JVM的堆内存中 但并不是这两个方法 // 被调用的时候立即缓存 而是触发后面的action算子时 该RDD将会被缓存在计算阶段的内存中 并供后面重用 // TODO Cashe操作会增加血缘关系 但是不会改变原来的血缘关系 // Cashe方法可以将血缘关系修改 添加一个和缓存相关的依赖关系 该操作不安全 因为将数据放到内存中 因此另一个方法放到磁盘 // 中 persist cashe的底层调用的就是persist 但是它的持久化级别是 Memory_Only 一旦内存不够就丢弃数据 根据LRU算法 // StorageLevel 存储级别 : OFF_HEAP 堆外内存 /*System.gc() gc线程 - 守护线程 守护线程是为用户服务的线程 堆中的数据有10个g的垃圾 但是垃圾回收器不及时 又来了2 * 个g的数据 堆外内存其实就是不受JVM 管理的内存 是主动向OS 申请的内存 Java虚拟机 默认内存是最大内存的 1/64 * 最大内存是 1/4 */ // CachedPartitions: 1; MemorySize: 1648.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B val linesRDD = sc.textFile("text.txt") val lineArray = linesRDD.flatMap(_.split(" ")) val wordToOne = lineArray.map( t => { println("*****************************") (t, 1) } ) // 持久化 wordToOne TODO 如果持久化的话 持久化的文件只能自己用 而且使用完毕后 会删除 // wordToOne.cache() // wordToOne.persist(StorageLevel.DISK_ONLY_2)//磁盘 两个副本 // TODO 检查点 Spark可以将中间计算的结果保存到检查点中 让其他的应用 使用数据 // 检查点可以切断 血缘关系 。可以剥离出去 以文件的形式存储 // TODO 检查点为了数据的安全 会重新执行一遍作业 会执行两次 导致效率降低 为了解决这个问题 // 可以将检查点和缓存联合 使用 // TODO 缓存和检查点的区别: /* 1 Cashe缓存只是将数据保存下来,不切断血缘关系。CheckPoint检查点切断血缘关系依赖 * 2 Cashe缓存的数据通常存储在磁盘、内存等地方,可靠性低,Checkpoint的数据通存储在HDFS * 分布式文件系统中。可靠性高 * 3 建议对Checkpoint的RDD使用Cashe缓存,这样Checkpoint的job只需从Cashe缓存中读取数据即可 * 否则需要重新计算一次RDD*/ wordToOne.cache() wordToOne.checkpoint() //Checkpoint directory has not been set in the SparkContext val WordCount = wordToOne.reduceByKey(_ + _) println(WordCount.toDebugString) WordCount.collect().foreach(println) println(WordCount.toDebugString) println("--------------------------------") val groupRDD = wordToOne.groupBy(_._1) groupRDD.collect() sc.stop() /*Exception in thread "main" java.lang.IllegalStateException: SparkContext has been shutdown*/ } } object Patitioner_Test { def main1(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val lines = sc.makeRDD( List( ("ff14", "maomao"), ("af15", "lalafei"), ("ff14", "nanjing"), ("cf", "youxi") ), 2 ) // TODO 自定义分区 val myRDD = lines.partitionBy(new MyPartitioner) myRDD.saveAsTextFile("MPoutput") sc.stop() } /*TODO 自定义分区器 1 继承Partitioner 2 重写方法 */ class MyPartitioner extends Partitioner { // TODO 分区数量 override def numPartitions: Int = 3 // TODO 根据数据的key返回所在的分区编号 分区编号从0开始 override def getPartition(key: Any): Int = { key match { case "ff14" => 0 case "af15" => 1 case "cf" => 2 } } } // TODO 假如有两个连续的reduceByKey def main2(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val lines = sc.makeRDD( List( ("ff14", "maomao"), ("af15", "lalafei"), ("ff14", "nanjing"), ("cf", "youxi") ), 2 ) val rdd1 = lines.reduceByKey(_ + _) val rdd2 = rdd1.reduceByKey(_ + _) // 这个reduceByKey就不会走shuffle了 /*TODO reduceByKey 源码: * def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope { reduceByKey(defaultPartitioner(self), func) } TODO 如果没有传分区器 :new HashPartitioner(defaultNumPartitions) /** * Return a copy of the RDD partitioned using the specified partitioner. */ def partitionBy(partitioner: Partitioner): RDD[(K, V)] = self.withScope { if (keyClass.isArray && partitioner.isInstanceOf[HashPartitioner]) { throw new SparkException("HashPartitioner cannot partition array keys.") } if (self.partitioner == Some(partitioner)) { 双等号就是非空equals self 如果是true 直接返回自身 RDD不做任何处理 } else { new ShuffledRDD[K, V, V](self, partitioner) } } 默认的HashPartitioner重写了equals 比较的规则: override def equals(other: Any): Boolean = other match { case h: HashPartitioner => 如果分区器相同且分区个数相同,返回true h.numPartitions == numPartitions case _ => false } */ } def main3(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val lines = sc.makeRDD( List( ("ff14", "maomao"), ("af15", "lalafei"), ("ff14", "nanjing"), ("cf", "youxi") ), 2 ) // TODO 文件的保存和读取 lines.saveAsObjectFile("Objectoutput") val rdd1 = sc.objectFile[(String, String)]("Objectoutput") lines.saveAsSequenceFile("sequenceFileoutput") rdd1.collect().foreach(println) val rdd2 = sc.sequenceFile[String, String]("sequenceFileoutput") rdd2.collect().foreach(println) sc.stop() } def main4(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List(1, 2, 3, 4)) var sum = 0 //sum和Spark没有关系 而RDD是spark的数据处理模型 rdd.collect()可以将rdd返回给Driver端 // TODO 自定义的变量和RDD没有关系 RDD无法计算该变量 // TODO 累加器 分布式共享只写变量 long型累加器 double浮点型累加器 集合累加器 // 创建累加器 /*TODO kafka集群生产数据: kafkaProducer main线程将数据放到缓冲区 sender线程往broker发送 * 数据 producer端有一个双端队列 Dqueue 在缓冲区中 */ val sum1 = sc.longAccumulator("sum") rdd.foreach( //foreach返回值是Unit num => { // sum = sum + num // println(sum) //这样打印的话 打印的结果是 1 3 6 10 // TODO 使用累加器 获取累加器的结果 sum1.add(num) } ) // 获取累加器的结果 println("累加器的值" + sum1.value) println(sum) //Driver端打印sum sum为0 sc.stop() } def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List("scala", "scala", "scala", "scala", "scala", "scala", "spark", "spark" , "spark", "spark", "spark", "spark", "spark", "spark")) // TODO 采用累加器实现WordCount // 创建累加器 val accumulator = new wordCountAccumulator() // TODO 向Spark进行注册 spark将累加器结果进行返回 sc.register(accumulator, "wordCount") rdd.foreach( word => { // 将单词放到累加器中 accumulator.add(word) } ) // 获取累加器的累加结果 println(accumulator.value) sc.stop() } // 自定义数据累加器 1 继承AccumulatorV2 2 定义泛型 IN String Out Map[k,v] // 3 重写方法 (6) 3+3 三个计算相关 add merge value输入输出和 合并 三个状态相关 copy reset // isZero class wordCountAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] { private val wcMap = mutable.Map[String, Int]() //判断累加器是否为初始状态 no.3 必须返回true 返回false 会报错 override def isZero: Boolean = { wcMap.isEmpty /*copyAndReset must return a zero value copy*/ } // 复制累加器 no.1 override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = { new wordCountAccumulator() } //重置累加器 累加器之间是无法相互读取的 每个累加器执行的时候保证是全新的累加器 no.2 override def reset(): Unit = { wcMap.clear() } // 从外部向累加器中添加数据 override def add(word: String): Unit = { val oldCount = wcMap.getOrElse(word, 0) wcMap.update(word, oldCount + 1) } // Executor的计算结果返回到Driver进行合并 合并两个累加器的结果 V2表示第二个版本 override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = { other.value.foreach { case (word, cnt) => { val oldCnt = wcMap.getOrElse(word, 0) wcMap.update(word, oldCnt + cnt) } } } override def value: mutable.Map[String, Int] = wcMap } } object broadCast_Test{ def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local").setAppName("wordCount") val sc = new SparkContext(conf) val rdd = sc.makeRDD(List( ("a",1),("b",2) )) // val rdd1 = sc.makeRDD(List( // ("a",3),("b",4) // )) val map = mutable.Map[String,Int]( //map是 Driver端声明的 需要发给Executor 四个分区 四个 // task 如果是两个executor 每个executor两个task 一个task需要执行一遍map中的逻辑 // rdd只能将数据给task 传 我们需要将Driver端的数据传给Executor TODO 广播变量 // 分布式只读变量 ("a",3),("b",4) ) // TODO 使用广播变量 将数据放到Executor端的缓存中 减少数据的冗余 val bcMap = sc.broadcast(map) // val rdd3 = rdd.join(rdd1) // TODO Join操作可能会有笛卡尔积 也可能会有shuffle // rdd3.collect().foreach(println) //(a,(1,3)) (b,(2,4)) val rdd2 = rdd.map{ case (word,cnt) => { // 使用了广播变量 用的时候就去Executor的缓存中去取 如果没有 就将Driver中的数据拉取过来 val cnt1 = bcMap.value.getOrElse(word,0) //map查数据是很快的 底层有hash定位 (word,(cnt,cnt1)) } } rdd2.collect().foreach(println) sc.stop() } } object Tencent{ def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("Tencent") val sc = new SparkContext(conf) val rdd: RDD[String] = sc.textFile("tencent.txt") // ((uid,enum), value) val mapRdd = rdd.map( data => { val datas: Array[String] = data.split(";") for (i <- 0 to 2) { val rdd1 = ((datas(0).split(" = ")(1), datas(1).split(" = ")(1).replace("[", "").replace("]", "").split(",") (i)), datas (2)) } } ) mapRdd.collect().foreach(println) sc.stop() } }