Spark在进行计算的时候通常会包含以下几个步骤
- 创建SparkContext上下文对象
- 使用SparkContext加载数据创建RDD
- RDD的转换算子transfotmations
- RDD的行动算子actions
- RDD的缓存和持久化
1.创建SparkContext上下文对象
// SparkContext
// 创建SparkConf对象用于配置参数
val conf = new SparkConf
// 连接集群
// conf.setMaster("spark://host02:7077")
// 本地测试时可以使用local作为master
// 设置本地模式运行spark程序 允许使用2个cpu核心
conf.setMaster("local[2]")
// 设置应用程序的名称
conf.setAppName("WordCount")
//获取SparkContext对象
val sc = new SparkContext(conf)
2.使用SparkContext加载数据创建RDD
// 创建RDD
// 1. 使用scala集合创建RDD 通常用于测试
// 1.1 makeRDD
//函数声明
// def makeRDD[T: ClassTag](
// seq: Seq[T],
// numSlices: Int = defaultParallelism): RDD[T] =
val RDD: RDD[Int] = sc.makeRDD(1 to 10, 4)
val numPar = RDD.getNumPartitions
// println(numPar)
// println(rdd.count())
//1.2 parallelize
// parallelize是早期的SparkApi
// 由于单词太长,所以重新封装为makeRDD
// def parallelize[T: ClassTag](
// seq: Seq[T],
// numSlices: Int = defaultParallelism): RDD[T] =
// 2. 读取文件创建RDD
// 生产环境会将原始数据保存在HDFS上
// 使用SparkContext从HDFS读取数据创建RDD
// 2.1 读取文本文件
// 将文本文件按行读取 每行作为RDD的一个元素
val filePath = "xxxxxxx"
val RDD = sc.textFile(filePath)
// 2.2 读取SequenceFile
// Hadoop中有时会使用SequenceFile进行KV数据存储
// Spark为了兼容Hadoop提供了SequenceFile的解析方式
val RDD3: RDD[(String, String)] = sc.sequenceFile[String, String]("C:\\Users\\Amos\\Desktop\\output")
// 2.3 读取ObjectFile
sc.objectFile("C:\\Users\\Amos\\Desktop\\objectFile")
3.RDD的转换算子transfotmations
//这一步的目的就是将RDD使用转换算子处理,形成一个新的RDD传递给下一步操作
//常见的转化算子
// 1. map 映射
// 1.1 声明
// def map[U: ClassTag](f: T => U): RDD[U] =
// 1.2 参数
// f 一元函数
// f的参数是源RDD的元素类型
// f的返回值 是任意类型
// 1.3 返回值
// 一个新的RDD 泛型是f的返回值类型
// 1.4 作用
// 将源RDD中的元素依次传入f中
// 将f的返回值收集到新的RDD并返回
// 1.5 Eample
RDD.map(x=>{
val strings = x.split(" ")
strings.head
})
// 2. filter 过滤
// 2.1. 声明
// def filter(f: T => Boolean): RDD[T] =
// 2.2. 参数 源RDD的元素类型
// 2.3. 返回值 Boolean
// 2.4. 作用
// 保留满足条件的元素
// 2.5 Example
RDD.filter(x=>{
x.split(" ")(8) == "200"
})
// 3. flatMap 扁平化处理
// 3.1. 声明 👇集合类的超类
// def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] =
// 3.2. 参数
// f 一元函数 参数 源RDD元素类型
// 返回值 需要是集合类型
// 3.3. 返回值
// 3.4. 作用
// 将二维集合 压扁为一维集合
// 3.5 Example
val RDD1 = sc.makeRDD(List(List(1,2,3,4,5),List(4,5,6,7,8,9)))
val RDD2 = RDD1.flatMap(List[Int])
value.foreach(print)
// 4. mapPartitions 分区映射
// 1. 声明
// def mapPartitions[U: ClassTag](
// f: Iterator[T] => Iterator[U],
// preservesPartitioning: Boolean = false): RDD[U] =
// 2. 参数
// f 一元函数
// f 的参数是 迭代器 泛型为RDD的元素类型
// f 的返回值 迭代器 泛型任意类型
// 3. 返回值
// RDD[U]
// 4. 作用
// 每次将RDD中一个分区的全部元素装在迭代器中传入f
// 将f返回值中的元素 放回新RDD的对应分区
// 因为每次批量处理整个分区的数据 效率高于map
// 4.5 Example
//划分成两个分区
val RDD2 = sc.makeRDD(2 to 10, 2)
//单个处理
RDD2.map(_ + 1)
//批处理
RDD2.mapPartitions(_.map(_ + 1))
// 5. 自定义分区
// def partitionBy(partitioner: Partitioner): RDD[(K, V)] =
class MyPartitioner(num: Int) extends Partitioner {
override def numPartitions: Int = num
override def getPartition(key: Any): Int = key.asInstanceOf[Int] % numPartitions
}
val rdd5: RDD[Int] = sc.makeRDD(1 to 10, 2)
val rdd51: RDD[(Int, Int)] = rdd5.map(x => (x, x))
val rdd52: RDD[(Int, Int)] = rdd51.partitionBy(new MyPartitioner(3))
val rdd53: RDD[Int] = rdd52.map(_._1)
rdd53.mapPartitionsWithIndex((i, iter) => {
Iterator(i + ":" + iter.mkString("-"))
})
//6. sample 抽样
// 6.1. 声明
// def sample(
// withReplacement: Boolean, 相同元素是否可以抽取多次
// fraction: Double, 抽样比例,由于RDD是并发计算,所以抽样比例不能做到绝对精确
// 种子 设置随机抽样的初始值
// 相同种子可以获取相同的抽样结果
// seed: Long = Utils.random.nextLong): RDD[T] =
// 6.2. 参数
// 6.3. 返回值
// 与RDD类型相同
// 6.4. 作用
// 在海量数据样本处理时
// 需要通过sample抽取其中的部分数据进行逻辑测试
// 如果多次抽样希望得到相同的结果,可以手动设置随机种子
// 6.5 Example
val rdd6 = sc.textFile("xxxxx")
val rdd61: RDD[String] = rdd6.sample(false, 0.8,1)
//7. union 合并
//7.1. 声明
// def union(other: RDD[T]): RDD[T] =
//7.2. 参数
// 另一个与源RDD泛型相同的RDD
//7.3. 返回值
// 与源RDD泛型相同的RDD
//7.4. 作用
// 合并两个元素类型相同的RDD
//7.5 Example
val rdd71 = sc.makeRDD(1 to 5)
val rdd72 = sc.makeRDD(3 to 8)
val rdd73 = rdd71.union(rdd72)
// 8.intersection 交集
// rdd71 ∩ rdd72
// 8.1. 声明
// def intersection(other: RDD[T]): RDD[T] =
// 8.2. 参数
// 8.3. 返回值
// 8.4. 作用
// 获取泛型相同的两个RDD的交集
//8.5 Example
val rdd8 = rdd71.intersection(rdd72)
//9.groupByKey 通过键分组
// 9.1. 声明
// def groupByKey(): RDD[(K, Iterable[V])] =
// 9.2. 参数
// 9.3. 返回值
// 将相同key对应的所有value放到集合中返回
// 9.4. 作用
// 按照key对数据进行分组
//9.5 Example
val RDD = sc.makeRDD(List("k1"->"v1","k1"->"v2","k3"->"v3"))
RDD.groupByKey()
.foreach(println)
// 10. reduceByKey
// 10.1. 声明
//def reduceByKey(func: (V, V) => V): RDD[(K, V)] =
// 10.2. 参数
// func 二元函数
// 将源RDD中相同K对应的V进行聚合
// 10.3. 返回值
// 与源RDD类型相同
// 10.4. 作用
// 按照Key聚合Value
//10.5 Example
val RDD = sc.makeRDD(List("k1"->"v1","k1"->"v2","k3"->"v3"))
RDD.reduceByKey(_+_)
.foreach(println)
// 11. reduceByKey
// 11.1. 声明
// def sortBy[K](
// f: (T) => K,
// ascending: Boolean = true,
// numPartitions: Int = this.partitions.length) : RDD[T] =
// 11.2. 参数
// func 一元函数
// 按照源RDD中的K进行排序
// ascending 设置升降序
// numPartitions 设置排序后的分区个数
// 11.3. 返回值
// 与源RDD类型相同
// 11.4. 作用
// 按照Key进行排序,将需要排序的字段构建在二元组的key位置
//11.5 Example
val RDD = sc.makeRDD(List(56->"小明",34->"张华",79->"赵燕"))
RDD.sortBy(_._1,ascending = false)
.foreach(println)
//12.join 内连接
// 12.1.声明
// 12.1.1内连接
// def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] =
// 12.1.2 外连接-左连接
// def leftOuterJoin[W](
// other: RDD[(K, W)],
// partitioner: Partitioner): RDD[(K, (V, Option[W]))] =
// 12.1.3 外连接-右连接
// def rightOuterJoin[W](other: RDD[(K, W)]) : RDD[(K, (Option[V], W))] =
// 12.1.4 外连接-全连接
// def fullOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], Option[W]))] =
// 12.2.参数
// 另一个二元组作为泛型的RDD
// K的类型与原RDD一致 V的类型可以与原RDD不同
// 12.3.返回值
// 12.4.作用
// 内连接:对K相同的两个RDD进行内连接
// 外连接-左连接:以左边的RDD为主,左边的所有K连接以后都会在存在新的RDD中,右边的V存在则连接不存在使用None填充
// 外连接-右连接:以右边的RDD为主,右边的所有K连接以后都会在存在新的RDD中,左边的V存在则连接不存在使用None填充
// 外连接-全连接:按左右两个RDD的k为主,有则连接,无则使用None填充
// 12.5.Example
val RDD1 = sc.makeRDD(List("k1" -> "v1", "k2" -> "v2", "k3" -> "v3", "k4" -> "v4"))
val RDD2 = sc.makeRDD(List("k1" -> "v1", "k2" -> "v2", "k4" -> "v4", "k6" -> "v6"))
RDD1.join(RDD2)
RDD1.leftOuterJoin(RDD2)
RDD1.rightOuterJoin(RDD2)
RDD1.fullOuterJoin(RDD2)
.foreach(println)
// 13 cogroup
// 13.1.声明
// def cogroup[W1, W2, W3](other1: RDD[(K, W1)],
// other2: RDD[(K, W2)],
// other3: RDD[(K, W3)],
// partitioner: Partitioner)
// : RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))] =
// 13.2.参数
// 参数可以最多3个与源RDD具有相同K类型 不同V类型的RDD
// 13.3.返回值
// 13.4.作用
// 相当于对两个RDD先进行groupByKey在进行join 最多支持4个RDD同时进行操作
// 多用于多RDD的数据对比
// 13.5.Example
val RDD = sc.makeRDD(List(
"张三" -> 89,
"李四" -> 75,
"丽丽" -> 99,
"张三" -> 87,
"李四" -> 76,
"丽丽" -> 80
))
val RDD1 = sc.makeRDD(List(
"张三" -> 60,
"李四" -> 61,
"丽丽" -> 62,
"张三" -> 63,
"李四" -> 64,
"丽丽" -> 65
))
val res = RDD.cogroup(RDD1)
.foreach(println)
// 14 cartesian 笛卡尔积
// 14.1.声明
// def cartesian[U: ClassTag](other: RDD[U]): RDD[(T, U)] =
// 14.2.参数
// 任意泛型的RDD
// 14.3.返回值
// 两两组合
// 14.4.作用
// 将两个RDD中的数据进行两两组合
// 14.5.Example
val RDD = sc.makeRDD(1 to 3)
val RDD1 = sc.makeRDD(1 to 3)
RDD.cartesian(RDD1).foreach(println)
// 15 repartition 分区调整
// 底层调用coalesce 并且设置开启shuffle
// 好处是分区数据较为均衡 避免数据倾斜
// 缺点是产生网络IO压力,影响效率
// coalesce 分区调整
// 默认不开启shuffle 用于在不产生网络IO时缩减分区
//通常在增加分区数量,对分区文件做负载均衡时使用repartition开启shuffle
//在缩减分区数量时使用coalesce 关闭shuffle
// Example
val RDD = sc.makeRDD(1 to 6,3)
RDD.repartition(6)
.map(x=>x*x)
.coalesce(3)
4.RDD的行动算子actions
//这一步是将处理好的RDD进行进一步的操作,得到想要的返回结果 实际上所有的转换算子都是在为行动算子做服务
//常见的行动算子
// reduce
// 1.声明
// def reduce(f: (T, T) => T): T =
// 2.作用
// 对RDD元素进行聚合直接返回聚合结果
// 3.Example
val i = sc.makeRDD(List("k1" -> 1, "k1" -> 1, "k1" -> 1, "k2" -> 2, "k3" -> 3))
.map(x => x._2)
.reduce(_ + _)
println(i)
// collect
// 1.声明
// def collect(): Array[T] =
// 2.作用
// 对RDD元素进行收集到一个数组中返回
// 3.Example
val i = sc.makeRDD(List("k1" -> 1, "k1" -> 1, "k1" -> 1, "k2" -> 2, "k3" -> 3))
.map(x => x._2)
.collect()
println(i.toList)
// count
// 1.声明
// def count(): Long =
// 2.作用
// 对RDD元素进行计数
// 3.Example
println(sc.makeRDD(1 to 200)
.count())
// first
// 1.声明
// def first(): T =
// 2.作用
// 取出RDD中的第一个元素
// 3.Example
println(sc.makeRDD(1 to 200)
.first())
// take
// 1.声明
// def take(num: Int): Array[T] =
// 2.作用
// 取出RDD中的前N个元素放在数组中返回
// 3.Example
println(sc.makeRDD(1 to 200)
.take(5).toList)
// takeSample
// 1.声明
// def takeSample(
// withReplacement: Boolean,
// num: Int,
// seed: Long = Utils.random.nextLong): Array[T] =
// 2.作用
// 抽取RDD中N个元素返回一个数组,可以设置重复抽取和随机种子
// 3.Example
println(sc.makeRDD(1 to 200)
.takeSample(withReplacement = false,20).toList)
// takeOrdered
// 1.声明
// def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] =
// 2.作用
// 将RDD升序排序拿取前N个元素
// 3.Example
println(sc.makeRDD(1 to 200)
.takeOrdered(20).toList)
// saveAsTextFile 等
// 1.声明
// def saveAsTextFile(path: String): Unit =
// 2.作用
// 将RDD保存到文件系统当中可以是本地文件系统也可以是分布式文件系统,保存的格式如果想用HadoopFile需要传一个二元组
// 3.Example
sc.makeRDD("今天天气真好呀").saveAsTextFile("xxxx")
// saveAsTextFile 等
// 1.声明
// def saveAsTextFile(path: String): Unit =
// 2.作用
// 将RDD保存到文件系统当中可以是本地文件系统也可以是分布式文件系统
// 3.Example
sc.makeRDD(1 to 5).map(x=>(x,x))
.saveAsSequenceFile("xxxx") // 必须是二元组作为泛型的RDD才能保存为序列文件
// .saveAsObjectFile("xxxx")
// .saveAsTextFile("xxxx")
// countByKey
// 1.声明
// def countByKey(): Map[K, Long] =
// 2.作用
// 按照RDD中的K进行计数
// 3.Example
sc.makeRDD(List(
"a"->1,"a"->2,"a"->3,
"b"->1,"b"->2,"b"->3,
))
.countByKey()
// println
// 1.声明
// def foreach(f: T => Unit): Unit =
// 2.作用
// 遍历整个内容并对其进行输出或者打印
// 3.Example
sc.makeRDD(List(
"a"->1,"a"->2,"a"->3,
"b"->1,"b"->2,"b"->3,
))
.foreach(println)
5.RDD的缓存和持久化
//RDD在计算过程中,某一节点计算错误会从上游进行重新计算,为此如果将某一容易出现失误的RDD进行缓存则可以避免从头开始计算提高计算效率,缓存会随着此次计算的消亡而消亡
//1. cache
RDD.cache()
// cache底层调用空参的persist()
//2. persist()空参底层调用
//persist(StorageLevel.MEMORY_ONLY)
// rdd.persist(StorageLevel.MEMORY_ONLY)
// 存储级别
// 基于内存 如果内存足够可以将RDD放入内存,读写效率较高
// 基于磁盘 如果内存不足,将RDD缓存在磁盘文件中,可靠性更强
// 开启序列化 如果cpu计算资源过剩可以开启序列化提高存储效率
// 非堆 如果内存足够时,可选的性能最优方案,避免GC进行Heap中的对象管理
// 常用
// OFF_HEAP
// MEMORY_ONLY
// MEMORY_AND_DISK
//一些情况下为了复盘上一次计算过程通常会将某一次算子持久化的保存到本地或集群文件中这时候就需要用到RDD的持久化操作
// 3. checkpoint
// checkpoint可以彻底截断RDD从头计算的依赖,再重新计算时,可以直接到checkpoint目录中加载RDD数据
// 3.1 使用SparkContext上下文对象设置checkpoint目录路径
sc.setCheckpointDir("xxxxxxxx")
// 3.2 调用checkpoint方法
RDD.checkpoint()