小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文针对大数据中Spark计算引擎的转换算子内容进行讲解
推荐阅读时间:15-20分钟
Value类型算子
map算子
函数签名
def map[U: ClassTag](f: T => U): RDD[U]
函数说明
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
演示代码
val conf: SparkConf = new SparkConf().setAppName("test").setMaster("local[*]")
val sc = new SparkContext(conf)
val dataRDD: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val dataRDD1: RDD[Int] = dataRDD.map(
num => {
num * 2
}
)
val dataRDD2: RDD[String] = dataRDD1.map(
num => {
"" + num
}
)
println(dataRDD1.collect().mkString("\t"))
println(dataRDD2.collect().mkString("\t"))
第一个map是值得转换,原始list中每个值*2
第二个map是类型的转换,将list中每个值得类型从int类型转换为String类型
运行结果
2 4 6 8
2 4 6 8
mapPartitions算子
函数签名
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
mapPartitions是按分区进行处理数据 map是每次对一条数据进行处理 内存空间较大的时候推荐这样使用 mapPartitions和map的区别: map是对数据一个一个处理,mapPartitions是按照分区处理
演示代码
listRD.mapPartitions(datas=>{
datas.map(data=>data*2)
})
优点: mapPartitions算子效率优于map,减少了发送到执行器执行的交互(按分区交互,不是每条数据都交互)
缺点: 可能会出现内存溢出(大量数据涌入到一个分区内)
mapPartitionsWithIndex算子
函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
带有了分区号 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
演示代码
val res: RDD[(Int, String)] = dataRDD.mapPartitionsWithIndex {
case (num, datas) => {
datas.map((_, "分区号:" + num))
}
}
println(res.collect().mkString("\n"))
val dataRDD1 = dataRDD.mapPartitionsWithIndex(
(index, datas) => {
datas.map(index, _)
}
)
运行结果
(1,分区号:0)
(2,分区号:0)
(3,分区号:1)
(4,分区号:1)
flatMap算子
函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
函数说明
map:获取一个新元素(原本几个元素还是几个元素)
flatmap :获取一个或者多个新元素(比原来的元素多) flatmap 将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
演示代码
val lineArray = Array("hello you","hello me","hello world")
val lines = sc.parallelize(lineArray, 1)
val res1: RDD[Array[String]] = lines.map(_.split(" "))
val res2: RDD[String] = lines.flatMap(_.split(" "))
println("map:")
res1.foreach(
line=> println(line.mkString("\t"))
)
println()
println("flatMap:")
res2.foreach(
line=> println(line.mkString("\t"))
)
运行结果
map:
hello you
hello me
hello world
flatMap:
h e l l o
y o u
h e l l o
m e
h e l l o
w o r l d
glom算子
函数签名
def glom(): RDD[Array[T]]
函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变适用于以分区为单位的数据统计
演示代码
val rdd: RDD[Int] = sc.makeRDD(List(1,5,6,9,8,5,6,2,4,65,2),4)
val arrayrdd: RDD[Array[Int]] = rdd.glom()
arrayrdd.collect().foreach(array=>{
println(array.max)
})
}
groupBy算子
函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
函数说明
作用:分组,按照传入函数的返回值进行分组,将相同的key对应的值放入一个迭代器中。
演示代码
val rdd: RDD[Int] = sc.makeRDD(List(1,5,6,9,8,5,6,2,4,65,2))
val grouprdd: RDD[(Int, Iterable[Int])] = rdd.groupBy(i => i%2)
grouprdd.foreach(println)
filter算子
函数签名
def filter(f: T => Boolean): RDD[T]
函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
演示代码
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1 = dataRDD.filter(_%2 == 0)
sample算子
函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
函数说明
根据指定的规则从数据集中抽取数据
代码演示
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1 = dataRDD.sample(false, 0.5)
val dataRDD2 = dataRDD.sample(true, 2)
扩展:
抽取数据不放回(伯努利算法)
伯努利算法:又叫0、1分布。例如扔硬币,要么正面,要么反面。
具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要
第一个参数:抽取的数据是否放回,false:不放回
第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
第三个参数:随机数种子
抽取数据放回(泊松算法)
第一个参数:抽取的数据是否放回,true:放回;false:不放回
第二个参数:重复数据的几率,范围大于等于0.表示每一个元素被期望抽取到的次数
第三个参数:随机数种子
distinct算子
函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
对数据进行去重复 直接在需要去重的RDD使用该方法
val lineArray = Array("hello you","hello me","hello world","hello me")
val lines = sc.parallelize(lineArray, 1)
val value: RDD[String] = lines.distinct()
coalesce和repartition算子
函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
repartition实际上调用的就是coalesce,并且默认是进行shuffle的,coalesce可以选择是否进行shuffle过程
拓展:
1)如果N<M。
一般情况下N个分区有数据分布不均匀的状况,利用HashPartitioner函数将数据重新分区为M个,这时需要将shuffle设置为true(repartition实现,coalesce也实现不了)。
2)如果N>M并且N和M相差不多
(假如N是1000,M是100)那么就可以将N个分区中的若干个分区合并成一个新的分区,最终合并为M个分区,这时可以将shuff设置为false(coalesce实现),如果M>N时,coalesce是无效的,不进行shuffle过程,父RDD和子RDD之间是窄依赖关系,无法使文件数(partiton)变多。 总之如果shuffle为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的
3)如果N>M并且两者相差悬殊
这时你要看executor数与要生成的partition关系,如果executor数 <= 要生成partition数,coalesce效率高,反之如果用coalesce会导致(executor数-要生成partiton数)个excutor空跑从而降低效率。如果在M为1的时候,为了使coalesce之前的操作有更好的并行度,可以将shuffle设置为true。
sortby算子
函数签名
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
函数说明
该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为升序排列。排序后新产生的RDD的分区数与原RDD的分区数一致。如果想要降序排列可以有第二个参数,设置为false即可
演示代码
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.sortBy(num=>num, false, 4)
PartitionBy算子
函数签名
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
函数说明
分区器 将数据按照指定Partitioner重新进行分区。Spark默认的分区器是HashPartitioner
HashPartitioner分区规则就是将当前的key进行取余操作,是默认的分区器
RangePartitioner范围分区器,要求数据是可以比较大小的可以排序的,适用性低,
自定义分区器
演示代码
val listedd: RDD[(String, String)] = sc.makeRDD(
List(
("cba", "消息1"), ("nba", "消息2"), ("nba", "消息3"),
("cba", "消息4"), ("cba", "消息5"), ("wnba", "消息6")
),
1
)
val rdd1: RDD[(String, String)] = listedd.partitionBy(new MyPartition(2))
val rdd2: RDD[(Int, (String, String))] = rdd1.mapPartitionsWithIndex(
(index, datas) =>
datas.map(
data => (index, data)
)
)
println(rdd2.collect().foreach(println))
sc.stop()
}
//继承Partitioner
class MyPartition(num: Int) extends Partitioner {
//获取分区的数量
override def numPartitions: Int = num
//根据数据的key来决定数据在哪个分区中进行处理
//方法的返回值表示分区编号(索引)
override def getPartition(key: Any): Int = {
key match {
case "nba" => 0
case _ => 1
}
}
}
注意: 如果重分区的分区器和当前的RDD的分区器一样,则不进行任何的处理,不会再次重分区
本文到此结束,对于本文知识,后续还有一个专题系列,感兴趣的小伙伴请继续关注!