RDD的运算
RDD运算的步骤
对于RDD的运算是以下一个步骤。
- 创建RDD对象
- 经过一系列的transformations对RDD进行转换
- 调用action触发RDD的计算(延迟计算)
- 要使用spark,开发者需要编写driver用于调度运行Worker
- RDD的运算也是一种类似于装饰者模式。通过控制抽象将逻辑传给对象。
RDD的创建
在Spark中创建RDD的方式可以分为3种:
- 从集合创建RDD
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
- 从外部存储创建RDD
sc.textFile("c://a.txt")
- 从其他RDD转换
val rdd2: RDD[Int] = rdd1.map(x => x)
RDD的转换
经过transformations对RDD进行转换,这些算子可以分为单value的和多value的,类似于
单value,都是窄连接
map(func)
类似于scala中map,对RDD中的每一个元素进行映射,窄连接.主要用于调整RDD中元素的数据结构。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// 默认的切片数为前面的核心数
val rdd1: RDD[Int] = sc.parallelize(Array(1, 3, 4))
rdd1.map(s=>{math.sqrt(s)}).collect().foreach(println)
}
mapPartitions(func)
对每一个分区中的元素做映射,可能出现OOM,因为一次要将一个分区中的所有数据读入内存,并且只有当一个分区中的所有数据都处理完后才释放,极限情况下,内存会存两倍分区数的数据。出现OOM的概率变大。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// 默认的切片数为前面的核心数
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
rdd1.mapPartitions(it=>it.map(math.pow(_,2))).collect().foreach(println)
rdd1.map(
// 建立到mysql的连接
// 从mysql读数据
// 一个rdd钟的每个元素操作时做一次操作
t=>t
)
rdd1.mapPartitions(it=>{
// 建立到mysql的连接
// 从mysql读数据
// 一个rdd钟的每个每个操作时做一次操作
// 原来数据处理完才能释放原来的数据,导致oom
// 效率比较高
it
})
}
mapPartitionsWithIndex(func)
对每一个分区中的元素做一次映射,最外层的映射是从集合到集合,里面还有一个映射是对集合的操作,调用的map是scala中的map.
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// 默认的切片数为前面的核心数
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
rdd1.mapPartitions(it=>it.map(math.pow(_,2))).collect().foreach(println)
rdd1.map(
// 建立到mysql的连接
// 从mysql读数据
// 一个rdd钟的每个元素操作时做一次操作
t=>t
)
rdd1.mapPartitions(it=>{
// 建立到mysql的连接
// 从mysql读数据
// 一个rdd钟的每个每个操作时做一次操作
// 原来数据处理完才能释放原来的数据,导致oom
// 效率比较高
it
})
}
flatMap(func)
类似于scala中的flatMap
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// 默认的切片数为前面的核心数
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20))
rdd1.flatMap(x=>Array(x,x)).collect().foreach(println)
println("------------")
// 使用flatMap实现过滤
rdd1.flatMap{
case x if x>50 =>Array(x)
case _ => Array[Int]()
}.collect().foreach(println)
sc.stop()
}
glom()
将一个分区中的元素用一个array存储
groupBy(func)
分区,注意的是分区需要shuffle性能低,二是分区之后每个区中间的数据的顺序不确定,其底层调用的依旧是groupBykey
def groupBy[K](f: T => K, p: Partitioner)(implicit kt: ClassTag[K], ord: Ordering[K] = null)
: RDD[(K, Iterable[T])] = withScope {
val cleanF = sc.clean(f)
this.map(t => (cleanF(t), t)).groupByKey(p)
}
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
//
val rdd1: RDD[Int] = sc.parallelize(Array(3,5,70,60,10,20))
// groupBy需要shuffle,分完组之后为之前的分区数。
// shuffle会降低spark的性能,尽量不用
rdd1.groupBy(x=>x%2).collect().foreach(println)
}
filter(func)
类似于scala中的过滤器
sample()
抽样器,在大数据场景下可能需要对部分数据进行抽样运算。
withReplacement: Boolean, 抽样时是否放回
fraction: Double, 抽样的比例
seed 随机数生成种子
note:如果抽样时可放回则抽样比例可以大于1,如果不可放回则抽样比例应该在0到1之间。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,70,60,10,70,60,10,70,60,10,70,60,10,70,60,1070,60,10,70,60,10))
// fraction 必须大于0小于1
// withReplacement表示抽取后是否放回
rdd1.sample(true,0.1).collect().foreach(println)
}
distinct
对数据进行去重操作
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
// 去重
rdd1.distinct().collect.foreach(println)
//用Ordering进行比较
}
coalasce和repartition
将分区设置为指定数量,coalasce中含有一个是否shuffle的参数,该参数默认为false,在false的情形下,通过coalasce不能减少分区数,但是如果将shuffle设置为true,则可以将分区数进行增加。而这正式repartition的底层实现逻辑。
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[3]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
// 减少分区
println(rdd1.getNumPartitions)
println(rdd1.coalesce(2).getNumPartitions)
// 默认没有shuffle,所以直接增加分区不行,会失败,
println(rdd1.coalesce(4).getNumPartitions)
// 如果要增加分区必须shuffle
println(rdd1.coalesce(4,true).getNumPartitions)
// 也可以使用repartition增加分区,底层调用的coalesce
println(rdd1.repartition(4).getNumPartitions)
}
sortBy
很难,又很简单的一个函数,用于排序,要通过shuffle.
简单用法,一重排序。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
// 降序
rdd1.sortBy(x=>x,true).collect().foreach(println)
// 升序
rdd1.sortBy(x=>x,false).collect().foreach(println)
}
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.parallelize(Array("aa","bb","abc","abcde"))
rdd1.sortBy[(Int,String)](x=>(x.length,x),false).collect().foreach(println)
val value: RDD[String] = rdd1.sortBy(x => (x.length, x))(Ordering.Tuple2(Ordering.Int.reverse, Ordering.String.reverse), ClassTag(classOf[(Int, String)]))
value.collect().foreach(println)
}
pipe
管道,针对每个分区,把 RDD 中的每个数据通过管道传递给shell命令或脚本,返回输出的RDD。一个分区执行一次这个命令. 如果只有一个分区, 则执行一次命令.
RDD的特殊玩法
flatMap过滤
双value
交集差集并集拉链
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
val rdd2: RDD[Int] = sc.parallelize(Array(30,50,7,6,1,10,20,30,30))
val rdd3: RDD[Int] = rdd1.union(rdd2)
// 并集
println("并集")
rdd3.collect().foreach(println)
(rdd1 ++ rdd2).collect.foreach(println)
// 交集
println("交集")
rdd1.interp(rdd2).collect.foreach(println)
// 差集,会将30减完
println("差集")
rdd1.subtract(rdd2).collect.foreach(println)
// 拉链,分区数一定要一样,每个分区类的个数不一致
println("拉链表")
// rdd1.zip(rdd2).collect.foreach(println)
rdd1.zipWithIndex().collect.foreach(println)
// 只要求分区一致,利用scala的灵活
println("灵活拉链表")
rdd1.zipPartitions(rdd2)((it1,it2)=>{
// 多余的扔掉
it1.zip(it2)
// it1和it2拉链,如果it1不够补-1,如果it2不够补-2
it1.zipAll(it2,-1,-2)
}).collect.foreach(println)
// 笛卡尔积
rdd1.cartesian(rdd2).collect().foreach(println)
}
Keyvalue
key-value的RDD具有许多独有的操作算子,这些算子十分的常用。
分区逻辑
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)
}
}
partitionBy
// PairRDDFunctions RDD没有partitionBy,通过隐式转换为PairRDDFunctions新增功能 对数据进行重新分区,可以自定义分区器
object PartitionBy {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// rdd1为
val rdd1: RDD[Int] = sc.parallelize(Array(30,50,70,60,10,20,30,30))
// RDD中隐式转换的设置将RDD转换后就有了reduceBykey的方法
// implicit def rddToPairRDDFunctions[K, V](rdd: RDD[(K, V)])
// (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null): PairRDDFunctions[K, V] = {
// new PairRDDFunctions(rdd)
// }
val rdd2: RDD[(Int, Int)] = rdd1.map((_, 1))
rdd2.partitionBy(new HashPartitioner(2){
override def getPartition(key: Any): Int ={
}
}).glom().collect().foreach(arr=>println("a: "+arr.mkString(", ")))
}
}
// 默认的hash分区器
def numPartitions: Int = partitions
def getPartition(key: Any): Int = key match {
case null => 0
case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
}
def nonNegativeMod(x: Int, mod: Int): Int = {
val rawMod = x % mod
rawMod + (if (rawMod < 0) mod else 0)
}
reduceByKey
(K,V)->(K,V),分区内的运算和分区间的运算一致且没有初值,
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
// rdd1为
val rdd1: RDD[Int] = sc.parallelize(Array(30, 50, 70, 60, 10, 20, 30, 30))
// 预聚合
val rdd2: RDD[(Int, Int)] = rdd1.map((_, 1))
rdd2.reduceByKey(_+_).collect().foreach(println)
}
groupByKey
按照key进行分组, (key,compactBuffer(value,value,value))。groupByKey与reduceByKey相比,后者多一个预聚合的过程。原因是groupByKey在运行时伴随着shuffle
val rdd1 = sc.parallelize(List(("a",1),("b",1),("b",1),("c",1),("c",1),("b",1))).groupbyKey
// rdd1 (("a",compactBuffer(1)),("b",compactBuffer(1,1,1)),("c",compactBuffer(1,1)))
aggregateByKey
有三个参数,第一个参数是初值,第二个参数是分区内函数和分区间函数
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
println("求最大值最小值")
rdd1.aggregateByKey((Int.MinValue,Int.MaxValue))(
{
case ((max,min),v) =>(max.max(v),min.min(v))
},{
case ((max,min),(max1,min1)) =>(max+max1,min+min1)
}
).collect().foreach(println)
// 求平均值
rdd1.aggregateByKey((0,0))(
{
case ((sum,count),v) => (sum+v,count+1)
},
{
case ((sum,count),(sum1,count1)) => (sum+sum1,count+count1)
}
).map{
case (k,(sum,count))=> (k,sum.toDouble/count)
}.collect().foreach(println)
// 求平均值2
rdd1.aggregateByKey((0.0,0.0))(
{
case ((avg,count),v) => ((avg*count+v)/(count+1),count+1)
},
{
case ((avg1,count1),(avg2,count2)) =>{
val d: Double = avg1*count1 + avg2*count2
val d1: Double = count1 + count2
(d/d1,d1)
}
}
).collect().foreach(println)
}
foldByKey
类似于aggregateByKey,只是分区内操作和分区间操作是一致的。
combineByKey
比aggregateByKey还要多一个参数,其优势在于初始值是可以运算出来的,需要加类型,其他不要加主要是有classTag,它可以在运行时推断类型
def combineByKey[C](
createCombiner: V => C, // 创建初始规则
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
partitioner, mapSideCombine, serializer)(null)
}
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))
sortByKey
根据key来进行排序
object SortByKey {
// 隐式转换Order
implicit private val value: Ordering[User] = new Ordering[User] {
override def compare(x: User, y: User): Int = x.age - y.age
}
// 对User按照年龄排序,需要重写Ordering
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(User(10,"d")::User(29,"e")::User(21,"a")::Nil)
val rdd2 = rdd1.map((_, 1))
rdd2.sortByKey().collect().foreach(println)
}
}
case class User(age :Int,name: String)
mapValues
只对keyvalue中的value进行map
join
join默认为内连接,两个rdd都有的进行连接,leftOuterJoin左连接=>(v,option[v]),fullOuterJoin=>(option[v],option[v])
object Join {
def main(args: Array[String]): Unit = {
// 两个RDD之间的连接,按照key相等作为连接条件,只连接都有的
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
var rdd1 = sc.parallelize(Array((1, "a"), (1, "b"), (2, "c"),(4,"d")))
var rdd2 = sc.parallelize(Array((1, "aa"), (3, "bb"), (2, "cc")))
//按照key相等作为连接条件,只连接都有的 内连接
val rdd3: RDD[(Int, (String, String))] = rdd1.join(rdd2)
// 左连接,左边的全部和右边的相同的
val rdd4: RDD[(Int, (String, Option[String]))] = rdd1.leftOuterJoin(rdd2)
// 全外连接
val rdd5: RDD[(Int, (Option[String], Option[String]))] = rdd1.fullOuterJoin(rdd2)
rdd3.collect().foreach(println)
rdd4.collect().foreach(println)
rdd5.collect().foreach(println)
sc.stop()
}
}
cogroup
作用:在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD,
test
package cn.lpc.RDD.KeyValue
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/*
1. 数据结构:时间戳,省份,城市,用户,广告,字段使用空格分割。
1516609143867 6 7 64 16
1516609143869 9 4 75 18
1516609143869 1 7 87 12
统计一个省份广告点击排名前三
*/
object Practice {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("CreateRDD")
val sc = new SparkContext(conf)
val originalData: RDD[String] = sc.textFile("c://agent.log")
val proAdsAndOne: RDD[((String, String), Int)] = originalData.map(s => {
val strings: Array[String] = s.split(" ")
((strings(1), strings(4)), 1)
})
val proAdsAndCount: RDD[((String, String), Int)] = proAdsAndOne.reduceByKey(_ + _)
val proAndAdsCount: RDD[(String, Iterable[(String, Int)])] = proAdsAndCount.map {
case ((pro, ads), count) => (pro, (ads, count))
}.groupByKey()
val result: RDD[(String, List[(String, Int)])] = proAndAdsCount.mapValues(
it => it.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
).sortByKey()
result.collect().foreach(println)
// 一口气版本
originalData.map(s => {
val strings: Array[String] = s.split(" ")
((strings(1), strings(4)), 1)
})
.reduceByKey(_ + _).map {
case ((pro, ads), count) => (pro, (ads, count))
}
.groupByKey().mapValues(
t => t.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
)
.sortByKey()
.collect()
.foreach(println)
// 统计点击用户数,而不是点击数该怎么做呢
sc.stop()
}
}
行动算子
RDD的转换算子在转换后会生成一个新的RDD,而行动算子的返回值不是RDD。这是两者的最大区别,只要传入的是func,最后都会送到driver端。
reduce(func)
通过func函数聚集RDD中的所有元素,
collect
再驱动程序中,以数组的形式返回数据集中的所有元素
count
计数
first
获取第一个分区的第一个元素
take(n)
取前n个元素
takeOrdered(n)
排序后取n个
aggregate(num)(func,func1)
与初值进行combine,然后聚合,注意的是aggregate,初值会计算n+1次,n个分区每个分区之内加一次,所有分区间计算时还会加上一次
fold(num)(func)
aggregate的简化操作,func和func1相同
saveAsTextFile
以文本形式存入文件系统之中
saveAsSequenceFile
SequenceFile是Hadoop专用的
saveAsObjectFile
用于将 RDD 中的元素序列化成对象,存储到文件中
countByKey()
针对(K,V)类型的 RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func)
对每个元素执行一次func,每个函数是在executor上执行,不是在driver执行,所以比较省内存