1 Spark核心编程
Spark计算框架为了能对数据进行高并发和搞吞吐的处理,封装了三大数据结构,分别是:
- RDD:弹性分布式数据集
- 累加器:分布式共享只写变量
- 广播变量:分布式共享只读变量
1.1 RDD
代表是一个弹性的,不可变,可分区,里面的元素可并行计算的集合
弹性:
存储的弹性:内存与磁盘的自动切换
容错的弹性:数据丢失可以自动修复
计算的弹性:计算出错重试机制
分片的弹性:可根据需要重新分片
分布式: 数据存储在大数据集群不同节点上
数据集: RDD封装了计算逻辑,并不保存数据
数据抽象: RDD是一个抽象类,需要子类实现
不可变: RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
可分区,并行计算
1.2 核心属性
1.2.1 分区列表
RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性
1.2.2 分区计算函数
计算时,使用分区计算函数对每个分区进行计算
1.2.3 RDD之间的依赖关系
RDD是计算模型的封装,计算模型进行组合时,需要建立依赖关系
1.2.4 分区器(可选)
只有数据为KV类型数据时,可以通过设定分区器自定义数据的分区,简单说就是给任务分配不同的分区
1.2.5 首选位置(可选)
可以根据计算节点的状态选择不同的节点位置进行计算
1.3 实现原理
RDD体现了装饰者设计模式,一层一层进行装饰,与IO基本类似
RDD执行是延迟加载的
只有当collect进行采集的时候,才会执行。
1.4 执行原理
执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个计算任务,将任务分配给资源不同节点,然后按照计算模型进行数据计算,最后得到计算结果
1)启动Yarn集群环境
2)Spark通过申请资源创建调度节点和计算节点
3)Spark框架根据需求将计算逻辑按分区划分成不同的任务
4)调度节点将任务根据计算节点状态发送到对应的计算节点进行计算
1.5 基础编程
1.5.1 RDD创建
1)从集合(内存)中创建RDD
Spark提供了两种方法:parallelize和makeRDD
object SparkCollect {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[Int] = sparkContext.parallelize(
List(1, 2, 4, 3)
)
val rdd2: RDD[Int] = sparkContext.makeRDD(
List(1, 2, 3, 4)
)
rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
sparkContext.stop()
}
}
从底层代码来看,makeRDD其实就是parallelize方法
2)从外部存储(文件)创建RDD
外部数据集创建RDD:本地的文件系统,所有Hadoop支持的数据集,比如HDFS,HBase等
object SparkCollect {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val fileRDD: RDD[String] = sparkContext.textFile("D:\\1input\\word")
fileRDD.collect().foreach(println)
sparkContext.stop()
}
}
3)从其他RDD创建
通过一个RDD运算完后,再产生新的RDD。
4)直接创建RDD(new)
1.5.2 RDD并行度与分区
读取内存数据时
对于:val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4,5,6), 3)
底层进行数据分区的时候关键源码:
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)
}
}
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
通过元组(start,end)进行操作,左闭右开截取数组数据,放入分区文件
读取文件数据的分区和读取内存中集合数据的分区是不一样的
读取文件时:
bytesRemaining:剩余的字节
splitSize:它总共的字节
goalSize:每个分区的数据的大小
按照hadoop的行读取规则,依照偏移量进行读取,不足一行读取一行:
详细的过程:根据自定义的分区数,或者默认的2个分区,然后(long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);)计算得出目标分区的每个分区的数据,bytesRemaining)/splitSize > SPLIT_SLOP 这个条件成立,则进去一个分区,与hadoop的行读取类似
* RDD转换算子
1)map
函数说明:将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
注意:分区不变,
mapValues操作的是只有value,不改变key
val dataRDD: RDD[Int] = rdd.map(
num => {
num * 2
}
)
2)mapPartitions
函数说明:以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据
返回的值需要可以迭代,可变可不变
注意:分区数据没有处理完就不能释放,容易造成内存溢出
3)mapPartitionsWithIndex
函数说明:处理的同时可以获取当前分区索引
val dataRDD1 = dataRDD.mapPartitionsWithIndex(
(index, datas) => {
datas.map(index, _)
}
)
4)flatMap
函数说明:将数据先进行扁平化,然后再进行映射处理,所以称作扁平映射
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Any] = sparkContext.makeRDD(
List(List(1, 2), 3, List(4, 5)), 1
)
val value: RDD[Any] = rdd.flatMap(
data => {
data match {
case list: List[_] => list
case d => List(d)
}
}
)
value.collect().foreach(println)
sparkContext.stop()
}
}
5)glom
函数说明:将同一个分区的数据转换为相同类型的数据处理
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[Array[Int]] = rdd.glom()
value.collect().foreach(
data =>{
println("--------------------")
for(i <- data){
println(i)
}
}
)
sparkContext.stop()
}
}
6)groupBy
函数说明:数据根据指定规则进行分组,分区默认不变,数据会被打乱,此过程为shuffle
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(
data => {
data % 10
}
)
value.collect().foreach(
data => {
println("\nkey is :" + data._1)
for(word <- data._2){
print(word + " ")
}
}
)
sparkContext.stop()
}
}
7)filter
函数说明:按照指定的规则将数据进行过滤,有可能出现数据倾斜
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[Int] = rdd.filter(_ % 2 == 0)
value.collect().foreach(println)
sparkContext.stop()
}
}
8)sample
函数说明:根据规则从数据集中抽取数据,第一个参数为是否放回,第二个参数是概率(放回为大于1,不放回取0~1之间),第三个参数是随机种子
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 3, 44, 55), 2
)
val value: RDD[Int] = rdd.sample(true, 2,1)
value.collect().foreach(
data =>{
print( data +" ")
}
)
println()
val value1: RDD[Int] = rdd.sample(true, 2,1)
value1.collect().foreach(
data =>{
print(data + " ")
}
)
}
}
9)distinct
函数说明:将数据集中的重复数据去重
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()
10)coalesce
函数说明:缩减分区,提高小数据集的执行效率
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 3, 44, 55), 3
)
val value1: RDD[Int] = rdd.coalesce(2)
val value: RDD[Int] = value1.mapPartitionsWithIndex(
(par, data) => {
println("分区号:" + par)
for (num <- data) {
print(num + " ")
}
println()
data
}
)
value.collect().foreach(println)
}
}
11)repartition
函数说明:内部执行的就是coalesce,只是将coalesce默认的参数shuffle为false改成了true,一个缩减分区用coalesce,扩大分区采用repartition
12)sortBy
函数说明:按照指定的规则,第一个参数指定规则,第二个参数为是否正序排列,第三个参数是分成多少个分区
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 9, 44, 55), 3
)
val value: RDD[Int] = rdd.sortBy(num => num%10, true, 6)
println(value.getNumPartitions)
value.collect().foreach(println)
}
}
13)pipe
函数说明:管道,针对每个分区,都调用一次shell脚本,返回输出的RDD
14)intersection
函数说明:对源RDD和参数RDD求交集后返回一个新的RDD
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 9, 44, 55), 3
)
val rdd2: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val rdd3: RDD[Int] = rdd.intersection(rdd2)
rdd3.collect().foreach(println)
15)union
函数说明:求并集
16)substract
函数说明:求差集,rdd1-rdd2
注意:数据类型不一致会出错
17)zip
函数说明:rdd1和rdd2两两打包形成元组
注意:类型可以不一致
18)partitionBy
函数说明:将数据按照指定的partitioner进行分区,Spark默认的分区器是HashPartitioner
默认分区器:
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
List((1,"aaa"),(2,"bbb"),(3,"ccc")), 3
)
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))
val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
(num, data) => {
data.map(
d => {
(num, d)
}
)
}
)
rdd3.collect().foreach(println)
sparkContext.stop()
}
}
自定义分区器:
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
List((1,"aaa"),(20,"bbb"),(30,"ccc")), 3
)
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(3))
val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
(num, data) => {
data.map(
d => {
(num, d)
}
)
}
)
rdd3.saveAsTextFile("output")
rdd3.collect().foreach(println)
sparkContext.stop()
}
class MyPartitioner(num:Int) extends Partitioner{
override def numPartitions: Int = num
override def getPartition(key: Any): Int = {
val zzuli: Int = key.toString.toInt
if(zzuli < 10)
0
else if(zzuli == 10)
1
else
2
}
}
}
19)reduceByKey
函数说明:将数据按照相同的key对value进行聚合操作
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 1),("a",22), ("b", 2), ("c", 3)))
val value: RDD[(String, Int)] = rdd.reduceByKey((a:Int,b:Int)=>{
a + b
})
value.collect().foreach(println)
sparkContext.stop()
}
}
20)groupByKey
函数说明:按照key来进行分组
val dataRDD1 =
sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey()
21)aggregateByKey
函数说明:将数据根据不同的规则进行分区计算和分区间计算
案例:取每个分区内相同key的最大值然后分区间相加
此算子是函数柯里化,存在两个参数列表
第一个参数列表中的参数表示初始值
第二个参数列表含有两个参数,一个为分区内的计算规则,一个分区间的计算规则
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Aggregate {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setAppName("zzuli").setMaster("local[*]")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(
("a", 1), ("a", 2), ("c", 3),
("b", 4), ("c", 5), ("c", 6))
, 2
)
val result: RDD[(String, Int)] = rdd.aggregateByKey(0)(
(x, y) => math.max(x, y), (x, y) => x + y
)
result.collect().foreach(println)
}
}
22)foldByKey
当分区内计算规则和分区间计算规则相同时,直接一个参数就能达到aggregate算子的效果
23)combineByKey
函数说明:对k-v型rdd进行聚集操作的聚集函数,类似于aggregate,combineByKey允许用户返回值的类型与输入不一致
练习:求每个key的平均值
第一个参数:改变数据格式
第二个参数:分区内计算
第三个参数:分区间计算
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val list = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
val sparkConf = new SparkConf().setAppName("zzuli").setMaster("local")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(list, 2)
val combineRdd: RDD[(String, (Int, Int))] = rdd.combineByKey(
(a: Int) => ((a, 1)),
(acc: (Int, Int), v: Int) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
val value: RDD[(String, Int)] = combineRdd.mapValues(
data => {
(data._1 / data._2)
}
)
value.collect().foreach(println)
sparkContext.stop()
}
}
24)sortByKey
函数说明:按照(k,v)中的k进行排序,如果是自定义的k值,需要实现Ordered接口
val value: RDD[(String, Int)] = rdd1.sortByKey(true)
25)join
函数说明:相当于sql中的内连接
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("a", 9), ("a", 8)))
val value: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
value.collect().foreach(println)
}
}
26)leftOuterJoin
函数说明:类似于sql中的左外连接,遍历每个(k,v),返回存在的值
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("s", 8)))
val value: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
value.collect().foreach(println)
}
}
27)cogroup
函数说明:在(k,v)的RDD上调用,返回(K,(iterable,Iterable))的RDD
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("c", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("c", 8)))
val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
value.collect().foreach(println)
}
}