RDD
- RDD概述
- RDD的创建
- RDD的操作
- transformation
- Action
- RDD分区
- RDD的持久化和checkpoint
- 持久化
- check point
- RDD的分区
- 键值对RDD
- 键值对RDD的创建
- RDD的数据读取
- 本地文件系统数据读写
- 分布式文件系统HDFS的数据读取
- json文件的数据读写
- 综合案例
spark核心编程RDD
RDD概述
弹性分布式数据集,代表一个弹性的、不可变的、可分区、里面的元素可并行计算的集合
- 弹性
- 存储的弹性:内存与磁盘相互切换
- 容错的弹性:数据丢失可以自动恢复
- 计算的弹性:计算出错重试机制
- 分片的弹性:可根据需要重新分片
- 分布式:数据存储在大数据集群的不同节点上
- 数据集:RDD封装了计算逻辑,并不保存数据
- 数据抽象:RDD是一个抽象类,需要子类具体实现
- 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
- 可分区、并行计算
五大属性:
- Partition:一组分片,数据集的基本组成单位。可以在创建RDD时指定RDD的分片个数。每个RDD会被一个计算任务处理,相互之间没有影响
- 分区函数:
- RDD之间的依赖关系:依赖关系分为宽依赖和窄依赖。部分数据丢失后,可以通过依赖关系重新计算
- Partitioner:分片函数/分区器:两种类型的分区函数:
- 基于哈希的Hash Partition,
- 基于范围的Range Partition。
- 只有对于key-value的RDD才会有partitioner。
- Preferred location:每个partition的优先位置
执行原理
spark执行时,先申请资源,然后将应用程序的数据处理逻辑分解为一个一个的计算任务,将计算任务分发到已经分配了资源的计算节点上,按照指定的计算模型进行数据计算,最后得出计算结果
RDD工作原理(在yarn上):
- 启动yarn集群
- spark通过申请资源申请调度节点和计算节点
- spark框架根据需求将计算逻辑根据分区划分成不同的任务
- 调度节点将任务根据计算节点状态发送到队形的计算节点进行计算
RDD的创建
- 从文件中创建::
- sc.textfile():以行为单位读取数据,读取的数据都是字符串
- sc.wholeTextFiles():以文件为单位
- 从内存中读取数据
- 通过并行集合(数组)创建:
- sc.parallelize()
- sc.markRDD()
- 分区:一行一行读取并分区,与子结束没关系,与偏移量有关系
RDD的操作
transformation
只发生转换
- filter:
val lines: RDD[String] = sc.textFile("data/01.txt")
val result: RDD[String] = lines.filter(lines => lines.contains("spark"))
result.collect().foreach(println)
- map:一个数据一个数据执行,分区内顺序执行,分区之间并行
val list: List[Int] = List(1, 2, 3)
val rdd: RDD[Int] = sc.makeRDD(list)
val result2: RDD[Int] = rdd.map(_ * 2)
result2.collect().foreach(println)
- mappartition:以分区为单位转换操作,将整个分区加载到内存中进行引用
- 传递一个迭代器,返回一个迭代器
- 内存占用较高
- mapPartitionWithIndex
- flatmap:
与map的区别: - groupByKey:返回一个新的(K,Iterable)形式的数据集(无参数传入)
val lines: RDD[String] = sc.textFile("data/01.txt")
val words: RDD[(String, Iterable[Int])] = lines.flatMap(_.split(" "))
.map(x => (x, 1))
.groupByKey()
words.collect().foreach(println)
运行结果:
- reduceByKey(func)
val lines: RDD[String] = sc.textFile("data/01.txt")
val words: RDD[(String, Int)] = lines.flatMap(_.split(" "))
.map(x => (x, 1))
.reduceByKey((a, b) => a + b)
words.collect().foreach(println)
运行结果:
Action
- count():统计RDD个数
- collect():执行
- take(int):
- foreach():对每一个RDD执行一样的操作
val array: Array[Int] = Array(1, 2, 3, 4, 5)
val rdd: RDD[Int] = sc.parallelize(array)
val count: Long = rdd.count()
val first: Int = rdd.first()
val take: Array[Int] = rdd.take(1)
val reduce: Int = rdd.reduce((a, b) => a + b)
println(count)
println(first)
println(reduce)
运行结果:
惰性机制:调用action中的函数才执行计算
RDD分区
分区可以增加程序的并行度,实现分布式计算
- 分区原则:分区个数 = 集群中CPU核心数目
- 如何分区:
- local模式:默认本地机器CPU数目
- Standlone模式/yarn模式:默认值大于2
- Apache Mesos模式:默认分区数为8
- 手动分区:
- 创建RDD时指定分区数量:sc.textFile(path,partitionNum)
- 使用reparitition方法重新设置分区:val rdd = data.repartition(2)mo
- 对
RDD的持久化和checkpoint
持久化
为了重用,或数据计算时间较长或数据较重要时使用持久化
使用cache() 或 persist()
cache默认保存到内存中
存储到磁盘:persist(storage Level.DISK_ONLT)
级别有不同:
- 仅在内存中缓存,且溢出不写入磁盘,丢弃:MEMORY_NOLY
- 仅写入磁盘:DISK_ONLY
- 仅写入磁盘,副本为2:MEMORY_ONLY_2
- 写入内存,内存不够溢写磁盘:MEMORY_AND_DISK
val lines: RDD[String] = sc.textFile("data/01.txt",2)
val wordcount: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map(x => (x, 1))
.reduceByKey((a, b) => a + b)
wordcount.cache()
// wordcount.persist()
wordcount.collect().foreach(println)
check point
检查点路径中保存的文件,执行完毕后不会被删除
设置保存路径:
sc.setCheckpointDir(“”)
check point与 cache对区别:
- cache将数据存储在内存中进行数据重用,会在血缘关系中添加新的依赖,出现问题从头读取
- persist将数据存储在磁盘文件中进行重用,涉及到io,性能较低,但数据安全。如果job执行完毕,临时保存的数据文件就会丢失
- check point将数据长久地保存在磁盘文件中进行数据重用,为了数据安全,一般会独立进行。为了提高效率,一般需要与cache联合使用:
rdd.cache()
rdd.checkpoint()
- checkpoint在执行过程中会切断血缘关系,重新建立新的血缘关系,等同于改变数据源
RDD的分区
**分区的作用:**增加程序的并行度,实现分布式计算
分区个数 = 集群中CPU核心数目
但Apache Mesos模式中,分区个数默认为8
设置分区数:创建RDD时可手动指定:val rdd = parallelize(array,2)
或通过repartition方法修改分区个数
val size: Int = wordcount.partitions.size
val repartitionRDD: RDD[(String, Int)] = wordcount.repartition(4)
val size2: Int = repartitionRDD.partitions.size
println(size)
println(size2)
运行结果:
分区类型
- HashPartitioner(哈希分区)
- RangerPartitioner(区域分区)
- 自定义分区
自定义分区器:
- 继承org.apache.spark.Partitioner
- numPartitions:Int 返回创建出来的分区数
- getPartiton(key: Any):Int 返回分区索引,从0开始
- 定义分区的函数
- 示例:根据key值的最后一位数字,写到不同的文件中:
package com.dw.rdd
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
object testPartitioner {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("MyPartitioner")
val sc: SparkContext = new SparkContext(conf)
val data: RDD[Int] = sc.parallelize(1 to 20)
data.map((_, 1)).partitionBy(new Mypartitioner(10)).map(_._1).saveAsTextFile("data/partitioner")
}
}
class Mypartitioner(numPartition: Int) extends Partitioner{
override def numPartitions: Int = numPartition
override def getPartition(key: Any): Int = {
key.toString.toInt % 10
}
}
键值对RDD
键值对RDD的创建
- 读取文件 => flatmap分词 => map构成键值对(_,1)
- 读列表 => map构成键值对(_,1)
##键值对RDD的操作
3. reduceByKey:相同key的值进行汇总求和``
4. groupByKey:相同key的值进行分组,但不汇总求和
5. 分别实现wordcount:
通过reduceByKey实现:
rdd = sc.textfile(“”)
word = rdd.flatmap(line => line.split(‘ ‘)).map((_,1))
WordCount = word.reduceByKey(_+_)
通过groupByKey实现:
```
rdd = sc.textfile(“”)
word = rdd.flatmap(line => line.split(‘ ‘)).map((_,1))
WordCount = word.groupByKey.map(x => (_.1,X.2.sum))
```
- 将所有的key返回生成一个新的RDD:rdd.keys
- 将所有的value返回生成一个新的RDD:rdd.values
- sortByKey():返回一个根据键排序的RDD
- sortBy():自定义根据什么东西排序
- map Values(func):对每一个value都应用func函数
- join:把RDD中元素key相同的进行连接
RDD的数据读取
本地文件系统数据读写
读:sc.textFile(“”)
写:rdd.saveAsTextFile(“”)
分布式文件系统HDFS的数据读取
读:sc.textFile(“”)
写:rdd.saveAsTextFile(“”)
json文件的数据读写
读:sc.textFile(“”)
解析:JSON.parseFull(JsonString:String)
解析成功返回:Some(map: Map[String,Any])
解析失败返回:None
综合案例
package com.dw.rdd
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object sort {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("sort")
val sc: SparkContext = new SparkContext(conf)
sc.setCheckpointDir("checkpoint")
var index: Int = 0
val lines: RDD[(String, String)] = sc.wholeTextFiles("data/sort", 3)
val result: RDD[(Int, Int)] = lines.filter(_.trim().length > 0.)
.map(x => (x.trim().toInt, " "))
.partitionBy(new HashPartitioner(1))
.sortByKey()
.map(x => {
index = index + 11
(index, x._1)
})
result.saveAsTextFile("data/sortoutput")
// result.collect().foreach(println)
sc.stop()
}
}