spark算子
什么是算子
百度百科上对算子的解释是这样的,算子是一个函数空间到函数空间上的映射O:X→X。广义的讲,对任何函数进行某一项操作都可以认为是一个算子,甚至包括求幂次,开方都可以认为是一个算子,只是有的算子我们用了一个符号来代替他所要进行的运算罢了,所以大家看到算子就不要纠结,他和f(x)的f没区别,它甚至和加减乘除的基本运算符号都没有区别,只是他可以对单对象操作罢了(有的符号比如大于、小于号要对多对象操作)。又比如取概率P{X<x},概率是集合{X<x}(他是属于实数集的子集)对[0,1]区间的一个映射,我们知道实数域和[0,1]区间是可以一一映射的(这个后面再说),所以取概率符号P,我们认为也是一个算子,和微分,积分算子算子没区别。总而言之,算子就是映射,就是关系,就是变换。
spark算子分类
从大方向来说,Spark 算子大致可以分为以下两类
- Transformation算子:操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有Action 操作的时候才会真正触发运算。
- Action算子:这类算子会触发 SparkContext 提交作业(Job),并将数据输出 Spark系统。
根据Transaction处理数据类型的不同,也可以分为三类:
- Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Value型的数据。
- Key-Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Key-Value型的数据。
- Action算子,这类算子会触发SparkContext提交作业。
spark算子实战
Value数据类型的Transformation算子
算子 | 说明 |
---|---|
map | 将原来数据集中的每个元素经过用户自定义的函数转换为一个新的RDD(MappedRDD) |
flatMap | 与map类似,但每个元素输入项可以被映射到0个或多个输出项,最终将结果“扁平化”后输出 |
mapPartitions | map的输入函数是作用于RDD中每个元素,而mapPartitions的输入函数是作用于每个分区,也就是把每个分区中的内容作为整体来处理的。 |
glom | 将RDD的每个分区中的类型为T的元素转换为数组Array[T]。 |
union | 将两个RDD中的数据集进行合并,最终返回两个RDD的并集,若RDD中存在相同的元素也不会去重。 |
cartesian | 对两个RDD中的所有元素进行笛卡尔积操作。 |
groupBy | 生成相应的key,相同的放在一起。 |
filter | 对元素进行过滤,对每个元素应用f函数,返回值为true的元素在RDD中保留,返回为false将过滤掉。 |
distinct | 对RDD去重。 |
subtract | 去掉含有重复的项。 |
sample | 对RDD内的元素进行采样,并生成一个新的RDD,这个新的RDD只有原来RDD的部分数据。用户可以设定是否有放回的抽样、百分比、随机种子,进而决定采样方式。 |
takeSample | 上面的sample函数是一个原理,但是不使用相对比例采样,而是按设定的采样个数进行采样,同时返回结果不再是RDD,而是相当于对采样后的数据进行collect(),返回结果的集合为单机的数组。 |
cache、persist | 都用于将一个RDD进行缓存的,这样在之后使用的过程中就不需要重复计算了,可大大节省程序运行的时间。 |
为了让大家更好的了解算子,我对有些算子写了两个实战例子
1. map
实例1:
scala代码
val a = sc.parallelize(List(1,2,3,4,5),3)
val b = a.map(a => a*a)
b.collect()
结果:
python代码
a = sc.parallelize([1,2,3,4,5],3)
b = a.map(lambda a:a*a)
b.collect()
结果:
实例2
scala代码
val a = sc.parallelize(List("beijing", "shanghai", "guangzhou", "shenzhen", "hangzhou"), 3)
val b = a.map(_.length)
val c = a.zip(b)
c.collect
结果:
python代码
a = sc.parallelize(["beijing", "shanghai", "guangzhou", "shenzhen", "hangzhou"], 3)
b = a.map(lambda a:len(a))
c =a.zip(b)
c.collect()
结果:
2.flatMap
实例1:
scala代码
val a = sc.parallelize(List("Hello world", "My name is Patrick"))
val b = a.flatMap(a => a.split(" "))
b.collect()
结果:
python代码
a = sc.parallelize(['Hello world', 'My name is Patrick'])
b = a.flatMap(lambda a: a.split(" "))
b.collect()
结果:
为了让大家更好的理解map和flatMap的区别,这里给出了将此题改为map的输出,以更好的对比两者的区别
实例2:
scala代码
val a = sc.parallelize(1 to 10, 5)
a.flatMap(1 to _).collect
结果:
sc.parallelize(List(1, 2, 3), 2).flatMap(x => List(x, x, x)).collect
结果:
python代码
a = sc.parallelize(range(1,10+1), 5)
a.flatMap(lambda a:range(1,a+1)).collect()
结果:
3.mapPartitions
这里再简单说一下map和mapPartitions 的区别,map作用于RDD中每个元素,而mapPartitions作用于每个分区。假设有N个元素,存在于M个分区,执行map函数将被调用N次,而mapPartitions 将被调用M次,当在映射的过程中不断的创建对象时就可以使用mapPartitions ,比map的效率高很多。
比如当想数据库写入数据时,如果使用map就需要为每个元素创建connection对象,而mapPartitions 是需为每个分区创建connection对象。
实例1:
scala代码
val a = sc.parallelize(1 to 10,3)
a.mapPartitions(x=>x.filter(_%2 == 0)).foreachPartition(p=>{println(p.toList)
println("============")})
结果:
python代码
def func(x):
for i in x:
if i %2 ==0:
yield i
def eachPartition(partition):
print(list(partition))
print("======")
a = sc.parallelize(range(1,10),3)
a.mapPartitions(func ).foreachPartition(eachPartition)
结果:
4.glom
实例1:
scala代码
a = sc.parallelize(1 to 10 ,3)
a.glom().collect()
结果:
python代码
a = sc.parallelize(1 to 10 ,3)
a.glom().collect()
结果:
5.union
实例1:
scala代码
val a = sc.parallelize(1 to 3 ,2)
val b = sc.parallelize(4 to 8 ,2)
(a ++ b).collect
结果:
python代码
a = sc.parallelize(range(1,3+1) ,2)
b = sc.parallelize(range(4,8+1) ,2)
a.unionononon(b).collect()
结果:
实例2:
scala代码
val a = sc.parallelize(1 to 5 ,2)
val b = sc.parallelize(4 to 8 ,2)
(a ++ b).collect
结果:
python代码
a = sc.parallelize(range(1,5+1) ,2)
b = sc.parallelize(range(4,8+1) ,2)
a.union(b).collect()
结果:
6.cartesian
实例1:
scala代码
val a = sc.parallelize(List(1,2,3))
val b = sc.parallelize(List(4,5,6))
a.cartesian(b).collect
结果:
python代码
a = sc.parallelize([1,2,3])
b = sc.parallelize([4,5,6])
a.cartesian(b).collect()
结果:
7.groupBy
实例1:
scala代码
val list = List(("tom","F","20"),("lucy","F","19"),("rose","F","21"),("alex","M","22"),("lily","F","18"))
val a = sc.parallelize(list,3)
a.groupBy(x=> {if (x._2=="F" ) "Female" else "Male"}).collect
结果:
python代码
list = [("tom","F","20"),("lucy","F","19"),("rose","F","21"),("alex","M","22"),("lily","F","18")]
a = sc.parallelize(list,3)
a.groupBy(lambda x:"Female" if x[1]=="F" else "Male").map(lambda x: (x[0],x[1])).collect()[0][1].data
结果:
a.groupBy(lambda x:"Female" if x[1]=="F" else "Male").map(lambda x: (x[0],x[1])).collect()[1][1].data
结果:
8.filter
实例1:
scala代码
val a = sc.parallelize(1 to 10, 3)
val b = a.filter(_ % 2 == 0)
b.collect
结果:
python代码
a = sc.parallelize(range(1,10+1), 3)
b = a.filter(lambda x:x%2==0)
b.collect()
结果:
9.distinct
实例1:
scala代码
val c = sc.parallelize(List("hello","world","hello","spark","scala"), 2)
c.distinct.collect
结果:
python代码
c = sc.parallelize(["hello","world","hello","spark","scala"], 2)
c.distinct().collect()
结果:
10.subtract
实例1:
scala代码
val a = sc.parallelize(List("tom","lucy","lili","jack","rose"), 3)
val b = sc.parallelize(List("natasha","andy","jack","alice","lucy"), 3)
val c = a.subtract(b)
c.collect
结果:
python代码
a = sc.parallelize(["tom","lucy","lili","jack","rose"], 3)
b = sc.parallelize(["natasha","andy","jack","alice","lucy"],3)
c = a.subtract(b)
c.collect()
结果:
11.sample
实例1:
scala代码
val a = sc.parallelize(1 to 100, 3)
a.sample(false,0.1,0).collect
结果:
注:
sample接受三个参数,第一个参数withReplacement表示是否放回抽样,ture表示有放回,false表示无放回;
第二个参数fraction表示抽样比例;
第三个参数seed表示随机数。
python代码
import time
a = sc.parallelize(range(1 ,100), 3)
a.sample(False,0.1,0).collect()
结果:
12.takeSample
实例1:
scala代码
val a = sc.parallelize(1 to 100, 3)
a.takeSample(false, 20,0)
结果:
python代码
a = sc.parallelize(range(1 ,100), 3)
a.takeSample(False, 20,0)
结果:
13.cache、persist
实例1:
scala代码
val a = sc.parallelize(1 to 10,3).map(x=>x*1.0)
val b = a.map(x=>x*x)
b.cache()
val avg = b.reduce((x,y) => x+y) / b.count
println(avg)
结果:
python代码
import numpy as np
a = sc.parallelize(np.linspace(1.0, 10.0, 10),3)
b = a.map(lambda x: x**2)
b.cache() # Preserve the actual items of this RDD in memory
avg = b.reduce(lambda x,y:x+y) / b.count
print(avg)
结果:
注:缓存RDD结果对于重复迭代的操作非常有用,比如很多机器学习的算法,训练过程需要重复迭代。
Key-Value型Transformation算子将在spark算子实战(二)中介绍,Actions算子将在spark算子实战(三)中介绍
参考博文:
http://blog.youkuaiyun.com/u013980127/article/details/53046760
https://www.cnblogs.com/liuzhongfeng/p/5285613.html