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))
}
}