SparkCore

Hadoop与Spark的对比

hadoop适合一次性的计算,如果迭代磁盘交互会很漫长。
spark将作业的计算结果存放于内存中,结合先进的调度会大大提高就散效率。

Word Count

object WordCount {
def main(args: Array[String]): Unit = {
// spark
// 建立spark连接
val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
var sc = new SparkContext(sparkConf)
// 执行业务操作
// 读取文件,按行读取
val lines:RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data")
// 将数据进行拆分
val words = lines.flatMap(_.split(" "))
// 将数据根据单词进行分组,便于统计
val wG:RDD[(String, Iterable[String])] = words.groupBy(word => word)
// 对分组后的数据进行聚合
val wc = wG.map{
 case(word,list) => {
   (word,list.size)
 }
}
val tuples:Array[(String, Int)] = wc.collect()
println("===================================结果显示===============================")
tuples.foreach(println)
// 关闭连接
sc.stop()
}
}

写法2:

  def main(args: Array[String]): Unit = {
    // spark
    // 建立spark连接
    val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
    var sc = new SparkContext(sparkConf)
    // 执行业务操作
    // 读取文件,按行读取
    val lines:RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data")
    // 将数据进行拆分
    val words = lines.flatMap(_.split(" "))
    val wordToOne = words.map(
      world => (world, 1)
    )
    // 将数据根据单词进行分组,便于统计
    val arrays = wordToOne.groupBy(
       word => word._1
    )
    // 对分组后的数据进行聚合
    val wc = arrays.map{
      case (word, list) => {
        list.reduce(
          (t1,t2) => {
            (t1._1,t1._2 + t2._2)
          }
        )
      }
    }
    val tuples:Array[(String, Int)] = wc.collect()
    println("=======================结果显示=====================")
    tuples.foreach(println)
    // 关闭连接
    sc.stop()
  }

写法3:

def main(args: Array[String]): Unit = {
object WC02 {
  def main(args: Array[String]): Unit = {
    // spark
    // 建立spark连接
    val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
    var sc = new SparkContext(sparkConf)
    // 执行业务操作
    // 读取文件,按行读取
    val lines:RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data")
    // 将数据进行拆分
    val words = lines.flatMap(_.split(" "))
    val wordToOne = words.map(
      world => (world, 1)
    )
    // 将数据根据单词进行分组,便于统计
    val wc = wordToOne.reduceByKey(_+_)
    // 对分组后的数据进行聚合
    val tuples:Array[(String, Int)] = wc.collect()
    println("=======================结果显示=====================")
    tuples.foreach(println)
    // 关闭连接
    sc.stop()
  }

spark核心编程

前置知识
  • socket分布式计算
object Tdriver {
  def main(args: Array[String]): Unit = {
    val client = new Socket("localhost",999)
    val out = client.getOutputStream
    val objOut = new ObjectOutputStream(out)
    objOut.writeObject(new Task)
    objOut.close()
    client.close()
    out.flush()
  }
}
object TExecutor {
  def main(args: Array[String]): Unit = {
    val server = new ServerSocket(999)
    println("等待")
    val client = server.accept()
    val in = client.getInputStream
    val objIn = new ObjectInputStream(in)
    val task:Task = objIn.readObject().asInstanceOf[Task]
    task.compute()
    objIn.close()
    server.close()
  }
}
class Task extends Serializable {
  val datas = List(1,2,3)
  val logic:(Int) => Int = _*2
   //val logic = (num:Int) => {num*2}
  def compute(): Unit = {
    println("计算开始!")
    val ints = datas.map(logic)
    println("计算结束!")
    println(ints)
  }
}
  • 两个计算节点
    这是一段很冗余的代码却体现了基本的思想,其中Task封装的是数据,这就是数据结构,有了这个基础对于下面的数据结构理解会有帮助
class SubTask extends Serializable {
  var datas:List[Int] = _
  var logic:(Int) => (Int) = _
  def compute(): Unit = {
    println("计算开始!")
    val ints = datas.map(logic)
    println("计算结束!")
    println(ints)
  }
}
object TExecutor {
  def main(args: Array[String]): Unit = {
    val server = new ServerSocket(999)
    println("等待999")
    val client = server.accept()
    val in = client.getInputStream
    val objIn = new ObjectInputStream(in)
    val task:SubTask = objIn.readObject().asInstanceOf[SubTask]
    task.compute()
    objIn.close()
    server.close()
  }
}
object Executor2 {
  def main(args: Array[String]): Unit = {
    val server = new ServerSocket(888)
    println("等待888")
    val client = server.accept()
    val in = client.getInputStream
    val objIn = new ObjectInputStream(in)
    val task:SubTask = objIn.readObject().asInstanceOf[SubTask]
    task.compute()
    objIn.close()
    server.close()
  }
}
object Tdriver {
  def main(args: Array[String]): Unit = {
    val task = new Task
    val subTask1 = new SubTask
    val subTask2 = new SubTask
    subTask1.datas = task.datas.take(2)
    subTask2.datas = task.datas.take(2)
    subTask1.logic = task.logic
    subTask2.logic = task.logic
    val client1 = new Socket("localhost",999)
    val out1 = client1.getOutputStream
    val objOut1 = new ObjectOutputStream(out1)
    objOut1.writeObject(subTask1)
    objOut1.close()
    client1.close()
    out1.flush()

    val client2 = new Socket("localhost",888)
    val out2 = client2.getOutputStream
    val objOut2 = new ObjectOutputStream(out2)
    objOut2.writeObject(subTask2)
    objOut2.close()
    client2.close()
    out2.flush()
  }
}

三大数据结构

RDD(Resilient Distributed Dataset) : 弹性分布式数据集

作用准备计算逻辑和数据,最小的计算单元。
RDD的数据处理方式类似于IO流,采用装饰者模式,只有在调用的时候才会真正的执行。
RDD不保存数据,但是IO可以保持数据。
RDD中封装了数据分区,可以对数据进行分区,是弹性灵活的。
RDD中存在首选机制,会计算发给那个节点计算最优。
RDD的创建

  • 从集合中创建(内存)
object RDD_M {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)
    val seq = Seq[Int] (1,2,3,4)
    // 并行
    //val rdd:RDD[Int] = sc.parallelize(seq)
    // 在底层本质上调用了parallelize
    val rdd = sc.makeRDD(seq)
    println("结果展示》》》》》》》》》》》》》》》》》》》》》》》》")
    rdd.collect().foreach(println)
    sc.stop()
  }
}
  • 从文件中创建

object RDD_F {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)
    // 以行为单位
    val rdd: RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data")
    // val rdd: RDD[String] = sc.textFile("hdfsPath")
    // val rdd: RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data\\1*.txt")
    rdd.collect().foreach(println)
    sc.stop()
  }
}
  • 从外部存储
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)
    // 外部文件
    // 读取结果为一个元组,第一个元素为文件路径,第二个元素为数据
    val rdd: RDD[(String, String)] = sc.wholeTextFiles("E:\\IDEA_SPACE\\demo\\data")
    rdd.collect().foreach(println)
  }
  • 分区设定
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)
    val seq = Seq[Int] (1,2,3,4)
    // 并行
    //val rdd:RDD[Int] = sc.parallelize(seq)
    // 在底层本质上调用了parallelize,第二个参数为分区数
    // 如果没有传递为有一个默认值
    val rdd = sc.makeRDD(seq, 2)
    rdd.saveAsTextFile("E:\\IDEA_SPACE\\demo\\out")
    println("结果展示》》》》》》》》》》》》》》》》》》》》》》》》")
    rdd.collect().foreach(println)
    sc.stop()
  }

默认分区数,底层源码分析
因此默认会使用CPU的核数

 scheduler.conf.getInt("spark.default.parallelism", totalCores)

设置

 sparkConf.set("spark.default.parallelism", "5")

如果分区数过多,数据不够分区
源码

case _ =>
       val array = seq.toArray // To prevent O(n^2) operations for List etc
       positions(array.length, numSlices).map { case (start, end) =>
           array.slice(start, end).toSeq
       }.toSeq
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
// 循环计算0,……计算开始结束的范围
     (0 until numSlices).iterator.map { i =>
       val start = ((i * length) / numSlices).toInt
       val end = (((i + 1) * length) / numSlices).toInt
       (start, end)
     }
   }
 // 第二个参数为分区,这个参数表示的是最小的分区,实际的分区数目是进行计算
 // 其实底层用的是Hadoop的文件读取方式,分区也是用的Hadoop的分区机制
 val rdd: RDD[String] = sc.textFile("E:\\IDEA_SPACE\\demo\\data"3)
 // 默认分区,最小为2
 def defaultMinPartitions: Int = math.min(defaultParallelism, 2)

// 计算逻辑
public InputSplit[] getSplits(JobConf job, int numSplits)
throws IOException {
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);
...
for (FileStatus file: files) {
...
if (isSplitable(fs, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
...
}
protected long computeSplitSize(long goalSize, long minSize,
long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}

分区数据的分配
1.以行为单位进行读取,与字节数目无关
2.数据读取时以偏移量为单位,偏移量不会重新读取
3.每个分区偏移量范围计算
分析案例

如果数据为文件,则以文件为单位
数据:
1234567
89
0
总字节量=数字+换行回车(两个字节) = 14
偏移量
第一行:012345678  8也被读到了第一个分区,因为是按行读取
第二行:9 10 11 12
第三行:13 14 

分区一[0,7] 1234567
分区二[8,14] 
89
0

RDD方法

  • 转换:功能的补充和封装,将旧的RDD进行封装为新的RDD(flatMap,map)
  • 行动:触发任务的调度和作业的执行collect触发

RDD算子

转换算子(将一个旧的RDD转换为一个新的RDD)
  • map(将函数应用到每一个元素)
    rdd.map(mapFuction)
    RDD并行,数据一个一个执行逻辑,每一个数据执行全部逻辑,分区内数据执行是有序的。
    不同分区之间的数据执行是无序的,互不影响。
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("test")
    val sc =  new SparkContext(conf)
    val list = List(1, 2, 3, 4)
    val rdd: RDD[Int] = sc.makeRDD(list)
    val rdd1: RDD[Int] = rdd.map(num => {
      println(">>>>" + num)
      num
    })
    val rdd2: RDD[Unit] = rdd1.map(num => {
      println("####" + num)
    })
    rdd2.collect().foreach(println)
    sc.stop()
  }

在这里插入图片描述

  • mapPartitions(解决map一个一个执行的效率低下的问题)
    一次性执行一个分区的数据,但会将整个分区的数据加载到内存中,数据量较大时容易溢出。
def main(args: Array[String]): Unit = {
    // spark
    // 建立spark连接
    val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
    val sc = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val mapRdd = rdd.mapPartitions(
      // 此时iter代表整个分区的数据
      iter => {
       iter.map(
          _ * 2
        )
      }
    )
    mapRdd.collect()
    mapRdd.collect().foreach(println)
    println("**")
    // 关闭连接
    sc.stop()
  }

得到分区中的最大值

val list = List(1, 2, 3, 4)
   val rdd: RDD[Int] = sc.makeRDD(list, 2)
   val result: RDD[Int] = rdd.mapPartitions(
     iter => {
       List(iter.max).iterator
     }
   )

在这里插入图片描述
注:map与mappartition的区别为,一次处理一条数据和一次处理一批数据。

  • mapPartitionsWithIndex
    根据分区索引进行处理
def main(args: Array[String]): Unit = {
    // spark
    // 建立spark连接
    val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
    val sc = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val mapRdd = rdd.mapPartitionsWithIndex(
      // 返回迭代器
      (index,ints) => {
        // 保留分区1中的数据
        if(index == 1) {
          ints
        } el se {
          // 空集合Nil
          Nil.iterator
        }
      }
    )
    mapRdd.collect()
    mapRdd.collect().foreach(println)
    println("**")
    // 关闭连接
    sc.stop()
  }
  • flatMap
  // 原本的数据为集合的集合
    val rdd: RDD[List[Int]] = sc.makeRDD(List(List(1,2,3),List(4,5)))
    rdd.flatMap(
      // 返回一个集合,里面装有单个元素,扁平化
      list => {
        list
      }
    )
  • glom,将一个分区的数组作为一个数组,分区不变
  • groupby分组
    group会将数据打乱,并且重新组合至一块,这个操作为shuffle。
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val groups: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      num => num % 2
    )

在这里插入图片描述

  • filter 过滤
  val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val value: RDD[Int] = rdd.filter(
      // 偶数保留
      num => num % 2 == 0
    )
    println("****")
    value.collect().foreach(println)
  • sample
    数据倾斜时使用
 val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    // 数据抽取
    // withReplacement: true 放回,如为false第二次无法再抽取 抽取以后是否放回
    // 第二个参数:数据源中每条数据抽取的概率
    // 第三个参数:抽取数据时随机算法的种子,不传为随机
    // 如果不传入参数则使用系统的当前时间
    val value: RDD[Int] = rdd.sample(
      false,
      0.4,
      1
    )
  • distinct:去重
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4,4,2,1))
rdd.distinct().collect().foreach(println)
  • coalesce 缩小分区
    默认情况下,数据不会将分区中的数据打乱重组,可能会导致数不均衡
    如果想要数据均衡可以使用shuffle处理,将第二个参数设置为true
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
// 也可以扩大分区
val newRdd: RDD[Int] = rdd.coalesce(2)
newRdd.distinct().collect().foreach(println)

在这里插入图片描述
使用shuffle后数据会被打乱

  • repartition:扩大分区
// 底层为coalesce,shuffle默认为true
val newRdd: RDD[Int] = rdd.repartition(2)
  • sortBy
  • 集合操作:intersection,union,subtract
    两个RDD的类型需要相同
    求差集(从不同的集合出发,结果不一样)
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.subtract(dataRDD2)
  • zip
    两个RDD的类型可以不同
    但是两个数据源的分区需要保持一致,且分区中的数据数量相同
// 1,2,3,4
// 2,3,4,5
结果 (1,2),(2,3),(3,4),(4,5
  • partitionBy
    如果前后分区规则相同,不会执行分区操作
val rdd: RDD[(Int, String)] =
sc.makeRDD(Array((1,"aaa"),(2,"bbb"),(3,"ccc")),3)
import org.apache.spark.HashPartitioner
val rdd2: RDD[(Int, String)] =
rdd.partitionBy(new HashPartitioner(2))
  • reduceByKey:根据相同的key进行聚合操作
    操作为两两聚合,如果数据为一个时无法进行聚合
  • groupByKey
    将数据源中的相同key分在一个组中,形成一个对偶元祖,第一个key为key,第二个元素为value的集合
    groupByKey和reduceByKey的区别
    group时中间过程会存在落盘操作,因为要等待各个分区的数据处理完成,才能进行分组。
    reduceByKey,在分区中存在相同的key就可以提前聚合,减少落盘的数据提高效率。
    注解:group和reduceByKey,分区内和分区间都是相同规则的聚合操作。
  • aggregateByKey
    不同的分区可以使用不同的规则进行聚合
// aggregateByKey 算子是函数柯里化,存在两个参数列表
    // 1. 第一个参数列表中的参数表示初始值
    // 2. 第二个参数列表中含有两个参数
    // 2.1 第一个参数表示分区内的计算规则
    // 2.2 第二个参数表示分区间的计算规则
    val rdd = {
    sc.makeRDD(List(
      ("a",1),("a",2),("c",3),
      ("b",4),("c",5),("c",6)
    ),2)

    }
   //rdd.saveAsTextFile("E:\\IDEA_SPACE\\demo\\out1")
    val newRdd: RDD[(String, Int)] = rdd.aggregateByKey(0)(
      // zeroValue为初始值
      // 分区内的计算最大值
      (x, y) => math.max(x, y),
      // 分区之间的计算,求和
      (x, y) => x + y
    )

在这里插入图片描述

  • foldByKey
 sc.makeRDD(List(
      ("a",1),("a",2),("c",3),
      ("b",4),("c",5),("c",6)
    ),2)

    }
   //rdd.saveAsTextFile("E:\\IDEA_SPACE\\demo\\out1")
    val newRdd: RDD[(String, Int)] = rdd.foldByKey(0)(
      // zeroValue为初始值
      // 分区内的计算逻辑与分区之间的逻辑相同
      _+_
    )

在这里插入图片描述
aggregateByKey拓展应用

    // aggregateByKey 算子是函数柯里化,存在两个参数列表
    // 1. 第一个参数列表中的参数表示初始值
    // 2. 第二个参数列表中含有两个参数
    // 2.1 第一个参数表示分区内的计算规则
    // 2.2 第二个参数表示分区间的计算规则
    val rdd = {
    sc.makeRDD(List(
      ("a",1),("a",2),("b",3),
      ("b",4),("b",5),("a",6)
    ),2)

    }
     // 计算每个KEY的出现的平均
    // a,3 b,4
    // 返回值类型取决于zeroValue初始值
    // 第一个0为分区内的值
    // 第二个值为key出现的次数
    val value: RDD[(String, (Int, Int))] = rdd.aggregateByKey((0, 0))(
      // zeroValue为初始值
      (t, v) => {
        (t._1 + v, t._2 + 1)
      },
      (t1, t2) => {
        (t1._1 + t2._1, t2._2 + t1._2)
      }
    )
    // 0:("a",1),("a",2),("c",3) => (a,10)(c,10)
    // => (a,10)(b,10)(c,20)
    // 1:("b",4),("c",5),("c",6) => (b,10)(c,10)

    val result: RDD[(String, Int)] = value.mapValues {
      case (num, cut) => {

        println(num,cut)
        num / cut
      }
    }

在这里插入图片描述

  • combineByKey
val rdd = {
    sc.makeRDD(List(
      ("a",1),("a",2),("b",3),
      ("b",4),("b",5),("a",6)
    ),2)
    }
    val value: RDD[(String, (Int, Int))] = rdd.combineByKey(
      // 将相同key的第一个数据进行转换,第一个值不会参与计算
      v => (v,1),
      // 分区内的计算
      (t, v) => {
        (t._1 + v, t._2 + 1)
      },
      // 分区间的计算
      (t1, t2) => {
        (t1._1 + t2._1, t2._2 + t1._2)
      }
    )
    // 0:("a",1),("a",2),("c",3) => (a,10)(c,10)
    // => (a,10)(b,10)(c,20)
    // 1:("b",4),("c",5),("c",6) => (b,10)(c,10)

    val result: RDD[(String, Int)] = value.mapValues {
      case (num, cut) => {

        println(num,cut)
        num / cut
      }
    }
  • join
    相同的key连接到一起
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6)))
rdd.join(rdd1).collect().foreach(println)

在这里插入图片描述
如果key存在不相等则不会出现在结果中
如果key相等,则会一一匹配,类似于笛卡尔积,数据会膨胀

  • leftJoin
val dataRDD1 = sc.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = sc.makeRDD(List(("a",1),("b",2),("d",3)))
val rdd: RDD[(String, (Int, Option[Int]))] = dataRDD1.leftOuterJoin(dataRDD2)

在这里插入图片描述

  • cogroup
    分组连接
val dataRDD1 = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
val dataRDD2 = sc.makeRDD(List(("a", 1), ("b", 2), ("d", 3)))
val cgroup: RDD[(String, (Iterable[Int], Iterable[Int]))] = dataRDD1.cogroup(dataRDD2)
cgroup.foreach(println)

在这里插入图片描述

行动算子
  • reduce
    聚合RDD中的所有元素,先聚合分区内的数据,再聚合各个分区的数据
  • collect
    以数组 Array 的形式返回数据集的所有元素
  • count
    返回 RDD 中元素的个数
  • first
    返回 RDD 中的第一个元素
  • take
    返回一个由 RDD 的前 n 个元素组成的数组
  • takeOrdered
    排序后的前n个数组
  • aggregate
    分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合,初始值参与分区间和分区内的计算。
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)
// 将该 RDD 所有元素相加得到结果
// 第一个函数为分区内,第二个函数分分区间
//val result: Int = rdd.aggregate(0)(_ + _, _ + _)
val result: Int = rdd.aggregate(10)(_ + _, _ + _)

初始值会参与分区内的运算也会参与分区间的运算

  • flod
    折叠操作, aggregate 的简化版操作
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val foldResult: Int = rdd.fold(0)(_+_)
  • countByValue
 val rdd = sc.makeRDD(List(1, 2, 3, 4))
    val map: collection.Map[Int, Long] = rdd.countByValue()
    println(map)

在这里插入图片描述

  • countByKey
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1,
"b"), (3, "c"), (3, "c")))
// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
save算子
// 保存成 Text 文件
rdd.saveAsTextFile("output")
// 序列化成对象保存到文件
rdd.saveAsObjectFile("output1")
// 保存成 Sequencefile 文件
rdd.map((_,1)).saveAsSequenceFile("output2")
foreeach算子
val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
 var sc = new SparkContext(sparkConf)
 val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
 // 收集后打印,driver端执行
 rdd.map(num=>num).collect().foreach(println)
 println("****************")
 // 分布式打印
 // 遍历执行在excutor端,发送到不同的excutor
 // rdd中的逻辑是在excutor端执行的
 rdd.foreach(println)

序列化

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    // 遍历执行在excutor端,发送到不同的excutor
    // rdd中的逻辑是在excutor端执行的
    val user = new User
    rdd.foreach(
      num => {
        println(user.age + num)
      }
    )
  }

在这里插入图片描述
此处也证明了,foreach算子是在executor端执行的

  • case Class会在编译时序列化
  • RDD算子中传递函数是包含闭包操作,会进行检测外部的变量是否满足序列化。
  • 使用 Kryo 序列化,序列化更快,文件更小,使用时也要继承 Serializable 接口。
  • case会自动序列化。

每个RDD都会保存依赖关系

此时如果到某个RDD执行出错,就可以从上一个RDD进行恢复
在这里插入图片描述
RDD依赖关系
打印依赖关系

println(resultRDD.dependencies)
  • one to one (窄依赖)
    新的RDD的数据来源于一个新的RDD分区
    -shuffle dependency
    新的RDD来源于多个上游RDD分区,宽依赖

RDD阶段

  • 数据依赖为oneToone不需要划分阶段
  • 存在shuffle则需要进行阶段的划分
  • 阶段是数量= shuffle 依赖 + 1
  • 任务的数量等于最后Rdd的分区数量

RDD持久化

临时文件,执行完毕后删除。
Rdd中不存储数据,重用时会从头到尾在执行一遍。因此需要进行缓存,即将数据进行持久化,持久化操作是在算子触发时执行的。

// cache 操作会增加血缘关系,不改变原有的血缘关系
println(wordToOneRdd.toDebugString)
// 数据缓存。
wordToOneRdd.cache()
// 可以更改存储级别
//mapRdd.persist(StorageLevel.MEMORY_AND_DISK_2)

存储级别选择
在这里插入图片描述

RDD检查点

需要指定检查点路径落盘,执行结束后也不会删除。

// 设置检查点路径
sc.setCheckpointDir("./checkpoint1")
// 创建一个 RDD,读取指定位置文件:hello atguigu atguigu
val lineRdd: RDD[String] = sc.textFile("input/1.txt")
// 业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
word => {
(word, System.currentTimeMillis())
}
}
// 增加缓存,避免再重新跑一个 job 做 checkpoint
wordToOneRdd.cache()
// 数据检查点:针对 wordToOneRdd 做检查点计算
wordToOneRdd.checkpoint()
// 触发执行逻辑
wordToOneRdd.collect().foreach(println)

缓存和检查点区别
1) Cache 缓存只是将数据保存起来,不切断血缘依赖。 Checkpoint 检查点切断血缘依赖。
2) Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。 Checkpoint 的数据通常存储在 HDFS 等容错、高可用的文件系统,可靠性高。
3)建议对 checkpoint()的 RDD 使用 Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存中读取数据即可,否则需要再从头计算一次 RDD。
4)checkpoint等同于改变数据源。

Rdd分区器

    val rdd: RDD[(String, String)] = context.makeRDD(List(("cba", ""), ("nba", ""), ("", "")), 3)
    rdd.partitionBy(new Mypartioner)
class Mypartioner extends Partitioner{
  override def numPartitions: Int = 3
  override def getPartition(key: Any): Int = {
    if (key == "nba") {
      0
    } else if (key == "cba") {
      1
    } else {
      2
    }
  }
}
累加器:分布式共享只写变量
  • 系统累加器
 val rdd = sc.makeRDD(List(1,2,3,4,5))
    // 声明累加器
    var sum = sc.longAccumulator("sum");
    rdd.foreach(
      num => {
        // sum += num,如果这样进行汇聚则sum最终结果为0
        // 因为计算机结果并没有返回给driver端
        // 使用累加器
        sum.add(num)
      }
    )
    // 获取累加器的值
    println("sum = " + sum.value)

在这里插入图片描述
注意:转换算子中调用累加器,没有行动算子,就不会执行,出现不累加的情况。如果有多个行动算子会多加,行动算子触发一次执行一次累加,所有一般放在行动算子中进行操作。

  • 自定义累加器
 def main(args: Array[String]): Unit = {
    // spark
    // 建立spark连接
    val sparkConf = new SparkConf().setMaster("local").setAppName("WC")
    val sc = new SparkContext(sparkConf)
    val rdd = sc.makeRDD(List("zs","ly","wh","mz","ly","wh"))
    // 声明累加器
    val sum = new CustomAccumulator
    sc.register(sum, "sumW")
    rdd.foreach(
      num => {
        // sum += num,如果这样进行汇聚则sum最终结果为0
        // 因为计算机结果并没有返回给driver端
        // 使用累加器
        sum.add(num)
      }
    )
    // 获取累加器的值
    println("sum = " + sum.value)
  }
}
// 自定义累加器
class CustomAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {
  var mcMap = mutable.Map[String, Int]()
  override def copyAndReset(): AccumulatorV2[String, mutable.Map[String, Int]] = super.copyAndReset()
  override def toString(): String = super.toString()
  // 判断是否为初始值
  override def isZero: Boolean = {
    mcMap.isEmpty
  }
  // 复制
  override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
    new CustomAccumulator
  }
  // 重置集合
  override def reset(): Unit = {
    mcMap.clear()
  }
  override def add(v: String): Unit = {
    val newCut:Int = mcMap.getOrElse(v, 0) + 1
    mcMap.update(v, newCut)
  }
  // 合并多个累加器
  override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
    val cuMap = this.mcMap
    val otherMap = other.value
    otherMap.foreach{
      case(word:String, cnt:Int) => {
        val newC = cuMap.getOrElse(word, 0) + cnt
        cuMap.update(word, newC)
      }
    }
  }
  // 返回值
  override def value: mutable.Map[String, Int] = {
    mcMap
  }
}
广播变量:分布式共享只读变量

Spark中的广播变量可以将闭包的数据保存到Executor中,但是为只读,为分布式共享可读变量。

val rdd1 = sc.makeRDD(List( ("a",1), ("b", 2), ("c", 3), ("d", 4) ),4)
val list = List( ("a",4), ("b", 5), ("c", 6), ("d", 7) )
// 声明广播变量
val broadcast: Broadcast[List[(String, Int)]] = sc.broadcast(list)
val resultRDD: RDD[(String, (Int, Int))] = rdd1.map {
case (key, num) => {
var num2 = 0
// 使用广播变量
for ((k, v) <- broadcast.value) {
if (k == key) {
num2 = v
}
}
(key, (num, num2))
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值