分区器
Spark 目前支持 Hash 分区和 Range 分区,和用户自定义分区。Hash 分区为当前的默认 分区。分区器直接决定了 RDD 中分区的个数、RDD 中每条数据经过 Shuffle 后进入哪个分 区,进而决定了 Reduce 的个数。
- 只有 Key-Value 类型的 RDD 才有分区器,非 Key-Value 类型的 RDD 分区的值是 None
- 每个 RDD 的分区 ID 范围:0 ~ (numPartitions - 1),决定这个值是属于那个分区的。
object Spark_03 {
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.makeRDD(List(("a","aaa"),("b","bbb"),("c","ccc"),("d","ddd")))
//创建自定义分区器对象
val parRDD: RDD[(String, String)] = rdd.partitionBy( new MyPartitioner)
parRDD.saveAsTextFile("output")
//关闭环境
sc.stop()
}
}
//自定义分区器
//继承Partitioner类
//重写方法
//
class MyPartitioner extends Partitioner {
//分区数量
override def numPartitions: Int = 3
//返回数据分区索引(从0开始)
override def getPartition(key: Any): Int = {
key match { //设置分区
case "aaa" => 0
case "bbb" => 1
case _ => 2
}
}
}
文件读写与保存
Spark 的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。 文件格式分为:text 文件、csv 文件、sequence 文件以及 Object 文件; 文件系统分为:本地文件系统、HDFS、HBASE 以及数据库。
text 文件
// 读取输入文件
val inputRDD: RDD[String] = sc.textFile("input/1.txt")
// 保存数据
inputRDD.saveAsTextFile("output")
sequence 文件
SequenceFile 文件是 Hadoop 用来存储二进制形式的 key-value 对而设计的一种平面文件(Flat File)。在 SparkContext 中,可以调用 sequenceFile[keyClass, valueClass](path)。
// 保存数据为 SequenceFile
dataRDD.saveAsSequenceFile("output")
// 读取 SequenceFile 文件
sc.sequenceFile[Int,Int]("output").collect().foreach(println)
object 对象文件
对象文件是将对象序列化后保存的文件,采用 Java 的序列化机制。可以通过 objectFile[T: ClassTag](path)函数接收一个路径,读取对象文件,返回对应的 RDD,也可以通过调用 saveAsObjectFile()实现对对象文件的输出。因为是序列化所以要指定类型。
// 保存数据
dataRDD.saveAsObjectFile("output")
// 读取数据
sc.objectFile[Int]("output").collect().foreach(println)
累加器
累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在 Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后, 传回 Driver 端进行 merge(合并)。
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6))
//获取系统累加器
val sum: LongAccumulator = sc.longAccumulator("sum")
//进行累加
rdd.foreach(
i => {
//使用累加器
sum.add(i)
}
)
//获取累加器的值
println(sum.value)
sc.stop()
}
自定义累加器
object Io {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[String] = sc.makeRDD(List("word hello","spark java","hello java"))
//创建累加器对象
val accumulator = new MyAccumulator
//向spark进行注册
sc.register(accumulator,"wordCount")
rdd.foreach(
i => {
//数据累加
accumulator.add(i)
}
)
//获得累加器的累加结果
sc.stop()
println(accumulator.value)
}
}
//自定义累加器 :实现wordCount功能
//继承AccumulatorV2 再定义泛型 IN OUT
//重写方法
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String,Long]]{
private var wcMp = mutable.Map[String,Long]()
//判断是否为初始状态
override def isZero: Boolean = {
wcMp.isEmpty //为空则是初始状态
}
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
new MyAccumulator()
}
//重置累加器
override def reset(): Unit = {
wcMp.clear() //清空
}
//获取累加器计算值
override def add(word: String): Unit = {
val newC= wcMp.getOrElse(word, 0L) + 1
wcMp.update(word,newC)
}
//合并累加器
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
val map1 = this.wcMp
val map2 = other.value
map2.foreach{
case (word, count) => {
val newC: Long = map1.getOrElse(word,0L)+count
map1.update(word,newC)
}
}
}
//累加器结果
override def value: mutable.Map[String, Long] = {
wcMp
}
}
广播变量
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个 或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表, 广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务 分别发送。
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd1= sc.makeRDD(List(("a",1),("b",2),("c",3)))
val map = mutable.Map(("a",4),("b",5),("c",6))
//join会导致数据量呈几何增长,并且会影响shuffle的性能,不推荐使用
//rdd1.join(rdd2)
rdd1.map {
case (w, c) => {
val l: Int = map.getOrElse(w,0)
(w,(c,l))
}
}.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 rdd1= sc.makeRDD(List(("a",1),("b",2),("c",3)))
val map = mutable.Map(("a",4),("b",5),("c",6))
//封装广播变量
val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(map)
rdd1.map{
case(w,c) => {
val i: Int = bc.value.getOrElse(w,0)
(w,(c,i))
}
}.collect().foreach(println)
sc.stop()
}