Spark计算框架三大数据结构:
- RDD:弹性分布式数据集
- 累加器:分布式共享只写变量
- 广播变量:分布式共享只读变量
RDD
是最基本的数据处理模型。它代表一个弹性的、不可变的、可分区的、内部元素可并行计算的数据集。
- 弹性
- 存储:内存与磁盘的自动切换
- 容错:数据丢失可自动恢复
- 计算:计算出错的重试机制
- 分片:可根据需要重新分片
- 不可变:RDD中封装了计算逻辑,不可改变。需通过产生新的RDD,在里面封装计算逻辑。
- 可分区、并行计算
- 数据集:RDD封装计算逻辑,不保存数据
RDD核心属性:
- 分区列表:用于执行任务时并行计算。
- 分区计算函数:对每个分区进行计算。
- RDD之间的依赖关系:计算模型的封装。需要组合多个计算模型时,将多个RDD建立依赖关系。
- 分区器(可选):当数据为KV数据集时,可设定分区器自定义数据的分区。
- 首选位置(可选):计算数据时,可根据计算节点的状态选择不同的节点位置进行计算。
RDD编程
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val rdd1 = sparkContext.makeRDD(List(1,2,3,4)) //从集合创建
val rdd2 = sparkContext.textFile("/usr/local/text.txt") //从外部存储创建
rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
sparkContext.stop()
设置并行度(并行执行任务数量)
val rdd1 = sparkContext.makeRDD(List(1,2,3,4), 2)
数据分区规则Spark核心源码
// Sequences need to be sliced at the same set of index positions for operations
// like RDD.zip() to behave as expected
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end)
}
}
RDD转换算子
- Value类型
1. map
函数签名
def map[U: ClassTag](f: T => U): RDD[U]
函数说明
将待处理数据逐条进行映射转换,可以是类型或是值的转换。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = rdd.map(num => num * 2)
val rddString: RDD[String] = rdd.map(num => "" + num)
2,4,6,8
"1","2","3","4"
2. mapPartitions
函数签名
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
将待处理数据以分区为单位发送到计算节点进行处理,可进行数据过滤等处理操作。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = rdd.mapPartitions(num => num.filter(_ == 2))
2
二者区别:
a. 数据处理角度:map算子是对分区内数据逐个进行映射操作;mapPartitions算子以分区为单位进行批处理操作。
b. 功能角度:map算子仅对数据值和类型进行转换,不会减少或增多数据;mapPartitions算子需传递一个迭代器,并返回一个迭代器,元素个数可以改变。
c. 性能角度:map算子串行计算,性能较低;mapPartitions算子批处理计算,性能较高。但占用内存,可能会出现内存溢出错误。
3. mapPartitionsWithIndex
函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
与mapPartitions类似,但在处理同时可获取当前分区索引。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = rdd.mapPartitionsWithIndex(
(index, num) => {
num.map(index, _)
})
4. flatMap
函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
函数说明
将处理的数据扁平化后在进行映射处理。也称为扁平化映射。
val rdd: RDD[List[Int]] = sparkContext.makeRDD(List(List(1, 2), List(3, 4)))
val flatRdd: RDD[Int] = rdd.flatMap(list => list)
(1,2,3,4)
5. glom
函数签名
def glom(): RDD[Array[T]]
函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,且分区不变。
val rdd: RDD[List[Int]] = sparkContext.makeRDD(List(1,2,3,4),1)
val glomRDD: RDD[Array[Int]] = rdd.glom()
6. groupBy
函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
函数说明
将数据根据指定规则进行分组,分区默认不变,但是数据会被打乱重组(shuffle)。
val rdd: RDD[List[Int]] = sparkContext.makeRDD(List(1,2,3,4))
val groupRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
(0,CompactBuffer(2, 4))
(1,CompactBuffer(1, 3))
7. filter
函数签名
def filter(f: T => Boolean): RDD[T]
函数说明
将数据根据指定规则进行过滤,符合规则的数据保留。经过滤后分区不变,但可能会出现数据倾斜情况。
val rdd: RDD[List[Int]] = sparkContext.makeRDD(List(1,2,3,4))
val filterRDD: RDD[Int] = rdd.filter(_ % 2 == 0)
2,4
8. sample
函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
函数说明
将数据根据指定规则进行抽取。
val rdd: RDD[List[Int]] = sparkContext.makeRDD(List(1,2,3,4))
第一个参数表示是否放回。
第一个参数为false,则该参数只能为[0,1]。
第二个参数小于1则表示:为每个数字赋予一个概率,小于这个概率则选中。若为0则全不选,若为1则全选。若
val sampleRDD1: RDD[Int] = rdd.sample(false, 0.5)
第二个参数大于1则表示:期望每个数字被选中的次数。
val sampleRDD2: RDD[Int] = rdd.sample(true, 2)
9. distinct
函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
去重
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4,2,4))
val disRDD: RDD[Int] = rdd.distinct()
1,2,3,4
10. coalesce
函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null): RDD[T]
函数说明
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率。
当小任务过多时,可使用coalesce聚集方法,收缩合并分区,减少任务调度成本。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4),4)
val coalRDD: RDD[Int] = rdd.coalesce(2)
11. repartition
函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
调整分区大小,内部执行coalesce操作,shuffle默认为true。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4),2)
val reparRDD: RDD[Int] = rdd.repartiton(4)
12. sortBy
函数签名
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
函数说明
排序操作
第一个参数为排序函数
第二个参数true为升序,false为降序
第三个参数为重新分区数
val rdd: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4,2,4),6)
val sortRDD: RDD[Int] = rdd.sortBy(num => num, false, 4)
4,4,3,2,2,1
- 双Value类型
1. intersection/union/subtract
函数签名
def intersection(other: RDD[T]): RDD[T]
def union(other: RDD[T]): RDD[T]
def subtract(other: RDD[T]): RDD[T]
函数说明
求两个RDD的交集/并集/差集
val rdd1: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sparkContext.makeRDD(List(3,4,5,6))
val interRDD: RDD[Int] = rdd1.intersection(rdd2)
val unionRDD: RDD[Int] = rdd1.union(rdd2)
val subtractRDD: RDD[Int] = rdd1.subtract(rdd2)
3,4
1,2,3,4,5,6
1,2
2. zip
函数签名
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
函数说明
键值对合并。第一个RDD为key,第二个RDD为value。
val rdd1: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sparkContext.makeRDD(List(3,4,5,6))
val zipRDD: RDD[Int] = rdd1.zip(rdd2)
(1,3)
(2,4)
(3,5)
(4,6)
- Key-Value类型
1. partitionBy
函数签名
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
函数说明
按照指定的partitioner重新分区。默认为HashPartitioner
val rdd1: RDD[(Int, String)] = sparkContext.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)
val partRDD: RDD[(Int, String)] = rdd1.partitionBy(new HashPartitioner(2))
(2,bbb)
(1,aaa)
(3,ccc)
2. reduceByKey
函数签名
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
函数说明
按照相同的key对value进行聚合
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4)))
val reduceRDD: RDD[(String, Int)] = rdd1.reduceByKey(_ + _)
(aaa,5)(bbb,2)(ccc,3)
3. groupByKey
函数签名
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
函数说明
按照相同的key进行分组。若无参数,则默认为cpu核数。
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4)))
val groupRDD1: RDD[(String, Iterable[Int])] = rdd1.groupByKey()
val groupRDD2: RDD[(String, Iterable[Int])] = rdd1.groupByKey(2)
val groupRDD3: RDD[(String, Iterable[Int])] = rdd1.groupByKey(new HashPartitioner(2))
(aaa,CompactBuffer(1, 4))(bbb,CompactBuffer(2))(ccc,CompactBuffer(3))
4. aggregateByKey
函数签名
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
函数说明
按照相同的key进行聚合操作。
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4)))
参数为初始值。第一个表达式为计算分区内最大值,第二个表达式为分区间求和。
val aggRDD: RDD[(String, Int)] = rdd1.aggregateByKey(0)((x, y) => math.max(x, y), (x, y) => x + y)
(aaa,5)(bbb,2)(ccc,3)
5. foldByKey
函数签名
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
函数说明
分区内和分区间计算规则相同时,可替换为foldByKey
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4)))
val foldRDD: RDD[(String, Int)] = rdd1.foldByKey(0)((x, y) => x + y)
(aaa,5)(bbb,2)(ccc,3)
6. combineByKey
函数签名
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
函数说明
类似aggregateByKey但允许更改返回类型。
val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93),
("a", 95), ("b", 98))
val input: RDD[(String, Int)] = sparkContext.makeRDD(list, 2)
val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
(_, 1),
(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
(b,(286,3))
(a,(274,3))
7. sortByKey
函数签名
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)]
函数说明
根据key进行排序。
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4)))
val sortRDD: RDD[(String, Int)] = rdd1.sortByKey() //true
val sortRDD: RDD[(String, Int)] = rdd1.sortByKey(false)
(aaa,1)(aaa,4)(bbb,2)(ccc,3)
(ccc,3)(bbb,2)(aaa,1)(aaa,4)
8. join
函数签名
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
函数说明
将两个RDD具有相同key的元素进行连接。不同元素不做处理。
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4), ("ddd", 5)))
val rdd2: RDD[(String, String)] = sparkContext.makeRDD(List(("aaa", "100"), ("bbb", "200"), ("ccc", "300"), ("aaa", "400")))
rdd1.join(rdd2)
(aaa,(1,100))
(aaa,(1,400))
(aaa,(4,100))
(aaa,(4,400))
(bbb,(2,200))
(ccc,(3,300))
9. leftOuterJoin
函数签名
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
函数说明
相同key进行连接操作,不同key则保留rdd1的元素,且与none连接。
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4), ("ddd", 5)))
val rdd2: RDD[(String, String)] = sparkContext.makeRDD(List(("aaa", "100"), ("bbb", "200"), ("ccc", "300"), ("aaa", "400")))
rdd1.leftOuterJoin(rdd2)
(aaa,(1,Some(100)))
(aaa,(1,Some(400)))
(aaa,(4,Some(100)))
(aaa,(4,Some(400)))
(bbb,(2,Some(200)))
(ccc,(3,Some(300)))
(ddd,(5,None))
10. cogroup
函数签名
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
函数说明
返回(key, rdd1中value的Iterable, rdd2中value的Iterable)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("aaa", 1), ("bbb", 2), ("ccc", 3), ("aaa", 4), ("ddd", 5)))
val rdd2: RDD[(String, String)] = sparkContext.makeRDD(List(("aaa", "100"), ("bbb", "200"), ("ccc", "300"), ("aaa", "400")))
rdd1.cogroup(rdd2)
(aaa,(CompactBuffer(1, 4),CompactBuffer(100, 400)))
(bbb,(CompactBuffer(2),CompactBuffer(200)))
(ccc,(CompactBuffer(3),CompactBuffer(300)))
(ddd,(CompactBuffer(5),CompactBuffer()))
- RDD行动算子
1. reduce
函数签名
def reduce(f: (T, T) => T): T
函数说明
聚集所有元素。先分区内,再分区间。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val reduceRDD: Int = rdd.reduce(_ + _)
10
2. collect
函数签名
def collect(): Array[T]
函数说明
以数组形式返回数据集所有元素
val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
rdd.collect().foreach(println)
1,2,3,4
3. count
函数签名
def count(): Long
函数说明
返回RDD元素个数
val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
rdd.count()
4
4. first
函数签名
def first(): T
函数说明
返回RDD第一个元素
val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
rdd.first()
1
5. take
函数签名
def take(num: Int): Array[T]
函数说明
以数组形式返回RDD中前n个元素。
val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val array: Array[Int] = rdd.take(3)
println(array.mkString(","))
1,2,3
6. takeOrdered
函数签名
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
函数说明
先进行排序,然后以数组形式返回RDD中前n个元素。(默认升序排列)
val rdd: RDD[Int] = sparkContext.makeRDD(List(3, 1, 4, 2))
val array: Array[Int] = rdd.takeOrdered(3)
println(array.mkString(","))
1,2,3
若降序排列,需要传入一个Ordering实现类。实现内部的比较函数。
或直接使用top(n),返回最大n个元素
class MyOrdering extends Ordering[Int] {
override def compare(x: Int, y: Int): Int = {
if (x < y) 1 else if (x == y) 0 else -1
}
}
val rdd: RDD[Int] = sparkContext.makeRDD(List(3, 1, 4, 2))
val ordering: MyOrdering = new MyOrdering()
val array: Array[Int] = rdd.takeOrdered(3)(ordering)
val array: Array[Int] = rdd.top(3)
println(array.mkString(","))
4,3,2
查看Ordering源码。调用java中Integer比较函数。
7. aggregate
函数签名
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
函数说明
分区内数据与初始值进行聚合,然后分区间数据与初始值进行聚合。(分区内求和,分区间求最大)
val rdd: RDD[Int] = sparkContext.makeRDD(List(3, 1, 4, 2),4)
val result: Int = rdd.aggregate(0)(_ + _, (x, y) => math.max(x, y))
4
8. fold
函数签名
def fold(zeroValue: T)(op: (T, T) => T): T
函数说明
分区内与分区间使用相同的聚集函数。
val rdd: RDD[Int] = sparkContext.makeRDD(List(3, 1, 4, 2),4)
val result: Int = rdd.fold(0)(_ + _)
10
9. countByKey
函数签名
def countByKey(): Map[K, Long]
函数说明
统计key的个数
val rdd: RDD[(Int, String)] = sparkContext.makeRDD(List((1, "aaa"), (2, "bbb"), (3, "ccc"), (4, "aaa"), (1, "ddd")))
val result: collection.Map[Int, Long] = rdd.countByKey()
Map(1 -> 2, 2 -> 1, 3 -> 1, 4 -> 1)
统计value个数
val result: collection.Map[(Int, String), Long] = rdd.countByValue()
Map((3,ccc) -> 1, (1,ddd) -> 1, (4,aaa) -> 1, (1,aaa) -> 1, (2,bbb) -> 1)
10. save
函数签名
def saveAsTextFile(path: String): Unit
def saveAsObjectFile(path: String): Unit
def saveAsSequenceFile(
path: String,
codec: Option[Class[_ <: CompressionCodec]] = None): Unit
函数说明
val rdd: RDD[(Int, String)] = sparkContext.makeRDD(List((1, "aaa"), (2, "bbb"), (3, "ccc"), (4, "aaa"), (1, "ddd")),3)
保存为text文件
rdd.saveAsTextFile("output")
(1,aaa)
(2,bbb)(3,ccc)
(4,aaa)(1,ddd)
保存为object文件
rdd.saveAsObjectFile("output")
保存为sequence文件
rdd.saveAsSequenceFile("output")