Spark学习之路(七)——Spark高级编程(共享变量、分区、存储)

一、Spark的共享变量

1、Spark API提供了在集群中提供了两种创建和使用共享变量的机制:广播变量累加器

广播变量

2、广播变量的作用:在Spark运行时,通常情况下,数据会将副本分发到每个执行器(Executor)的任务(Task)中,当数据量很大时,这种数据处理方式会造成工作节点上内存和网络传输的浪费。而广播变量是由驱动器(Driver)程序设置的只读变量,只会将数据的副本分发给每个执行器(Executor)中,Executor中的所有任务都可以访问这些数据,但是只能读。
在这里插入图片描述
在这里插入图片描述
3、广播变量的操作:
①.broadcast()
语法:sc.broadcast(value)
功能:.broadcast()方法会在指定的SparkContext内创建一个Broadcast对象。

②.value()
语法:Broadcast.value()
功能:.value方法从广播变量中获取值。

③.unpersist()
语法:Broadcast.unpersist(blocking=False)
功能:.unpersist方法把广播变量从集群中所有保存该广播变量的工作节点的内存中移除。Blocking参数指定该操作是堵塞直至变量已经从所有节点删除,还是作为异步非堵塞操作执行。True为立即释放内存。

4.与广播变量相关的Spark配置:

配置项说明
spark.broadcast.compress指定在向工作节点传送广播变量时是否进行压缩(默认:True)
spark. broadcast.factory指定使用何种广播实现(默认:TorrentBroadcastFactory)
spark. broadcast.blockSize指定广播变量每个数据块的大小(默认:4M)
spark.broadcast.port指定驱动器的http广播服务器要监听的端口(默认:random)

5、广播变量的要点:
①使用广播变量避免了数据混洗操作;
②广播变量使用了一种高效而伸缩性强的点到点分发机制;
③每个工作节点只会复制一次数据;
④广播变量可以被多个任务多次使用;
⑤广播变量是序列化过的对象,可高效读取。
⑥广播变量只能在Driver端定义或修改,不能在Executor端定义或修改。

累加器

6、累加器:累加器由驱动器程序设置,可以由相应的SparkContext中运行任务的执行器更新。在Spark应用中,每成功完成一个任务只能更新一次累加器。工作节点把对累加器的更新量发送回驱动器(Driver)程序,只有Driver程序可读取累加器的值。累加器的值可以使用整型或浮点型。

7、累加器的操作:
①.accumulator()
语法:sc.accumulator(value,accum_param=None)
功能:.accumulator()方法在指定的SparkContext中创建Accumulator对象实例,并用设置value为初始值。accum_param参数用来自定义累加器。

②.value()
语法:Accumulator.value()
功能:.value ()方法获取累加器的值,只能在Driver程序中使用。

③自定义累加器
自定义累加器需要扩展AccumulatorParam类为一个自定义类,可以对标量数值以外的数据类型执行聚合操作,唯一的要求是所执行的操作需满足交换律和结合律,即改变操作顺序或改变前后次序都不会影响结果。自定义累加器常用来把向量累加为Python中的列表或字典。自定义累加器也可以用来连接字符串,如下:

from pyspark import AccumulatorParam
class VectorAccumulatorParam(AccumulatorParam):
	def zero(self, value):
		dict1 = {}
		for i in range(0, len(value)):
			dict1[i] = 0
			return dict1

	def addInPlace(self, val1, val2):
		for i in val1.keys():
			val1[i] += val2[i]
		return val1

rdd0 = sc.parallelize([{0: 0.3, 1: 0.8, 2: 0.4},{0: 0.2, 1: 0.4, 2: 0.2}])
vector_acc = sc.accumulator({0: 0, 1: 0, 2: 0}, VectorAccumulatorParam())

def mapping_fn(x):
	global vector_acc
	vector_acc += x

rdd0.foreach(mapping_fn)
print(vector_acc.value)

#output: {0: 0.5, 1: 1.2000000000000002, 2: 0.6000000000000001}

8、累加器的结果可能错误
如果在转换操作里使用累加器,可能会获得错误的结果(如:在map()操作内调用累加器执行累加操作计算结果时)。阶段重测或预测执行会导致累加器重复计算,造成计算结果错误。如果要求绝对的正确性,应仅在由Spark驱动器执行的行动操作中使用累加器(如:foreach())。如果只是对超大数据集进行大致或定性的统计,完全可以在转换操作中更新累加器。

二、分区

1、在使用HDFS时,Spark会把每个数据块(HDFS中一个数据块一般是128M)作为一个分区。

2、.groupByKey()、.reduceByKey()等操作会导致数据混洗,且未指定numPartitions值,该操作产生的分区数将等于配置项spark.default.parallelize对应的值。若未设置spark.default.parallelize的值,该操作产生的分区数与当前RDD谱系中的上游RDD中最大的分区数相等。

3、分区函数:
①.getNumPartitions()
语法:rdd.getNumPartitions()
功能:.getNumPartitions()返回返回RDD的分区数。

②.partitionBy()
语法:rdd.partitionBy(numPartitions,partitionFunc=portable_hash)
功能:.partitionBy()是转化操作,返回的RDD与输入RDD的数据相同,但分区数变为numPartitions参数指定的分区数,默认使用portable_hash函数(HashPartitioner)进行分区。

③.repartition()
语法:rdd.repartition(numPartitions)
功能:.repartition()方法返回的RDD与输入RDD的数据相同,但分区数变为numPartitions参数指定的分区数。repartition()方法会引起数据混洗

④.coalesce()
语法:rdd.coalesce(numPartitions,shuffle=False)
功能:.coalesce()方法返回RDD的分区数变为numPartitions参数指定的分区数。该方法允许用户用布尔型的shuffle参数控制是否触发混洗。.coalesce(n,shuffle=True)等价于.repartition(n)
#注:.coalesce()方法是对.repartition()优化的实现。与.repartition()不同的是,.coalesce()让用户能更多地混洗行为,同时多数情况下避免数据移动。.coalesce()只允许使用比输入RDD更少的目标分区数

⑤.repartitionAndSortWithinPartitions()
语法:rdd. repartitionAndSortWithinPartitions(numPartitions=None, partitionFunc=portable_hash, ascending=True, keyfunc=lambda function)
功能:.repartitionAndSortWithinPartitions()方法把输入的RDD根据partitionFunc参数指定的函数,重新分区为numPartitions参数指定的分区数。在生成的每个分区中,记录根据键按照keyfunc参数定义的函数和ascending参数定义的顺序排序。

kvrdd0=sc.parallelize([((1,99),’A’),((1,101),’B’),((2,99),’C’),((2,101),’D’)], 2)
kvrdd1 = kvrdd0.repartitionAndSortWithinPartitions(numPartitions=2, ascending=False, keyfunc=lambda x:x[1])
kvrdd1.glom().collect()

#output:[[((1,101), ’B’), ((1,99), ’A’), ((2,101),’D’), ((2,99),’C’)]]

⑥.foreachPartition ()
语法:rdd.foreachPartition(func)
功能:.foreachPartition()是行动操作,类似于行动操作foreach(),会将func参数指定的函数应用到RDD每个分区。

kvrdd0=sc.parallelize([((1,99),’A’),((1,101),’B’),((2,99),’C’),((2,101),’D’)], 2)

def f(x):
	for rec in x :
		print(rec)
#注意:在Spark交互式编程时,自定义函数编写完成后,连续输入两次“Enter”(换行键),跳出函数定义。

kvrdd0.foreachPartition(f)

#output:
#((1, 99), 'A')
#((1, 101), 'B')
#((2, 99), 'C')
#((2, 101), 'D')

⑦.glom()
语法:rdd.glom()
功能:.glom()方法把RDD每个分区中的元素合并为一个列表,以新的RDD返回。

⑧.lookup()
语法:rdd.lookup(key)
功能:.lookup()方法返回RDD中与key参数指定的键匹配的数据的列表。若操作的RDD分区方式是已知的,则lookup()会利用它来收紧对键所属的分区的搜索。

⑨.mapPartitions()
语法:rdd.mapPartitions(func,preservesPartitioning=False)
功能:.mapPartitions()方法将func参数指定的方法用于输入RDD的每个分区,返回一个新的RDD。.mapPartitions()方法的优势在于它只对每个分区使用一次指定函数,而不是每个元素一次。

三、存储

1、RDD存储级别

存储级别说明
MEMORY_ONLY仅把RDD分区存储在内存中。
MEMORY_AND_DISK将内存中存不下的RDD分区存储到硬盘上。
MEMORY_ONLY_SER(Java和Scala)把RDD分区以序列化的对象的形式存储在内存中。使用该选项可节省内存,因为序列化的对象会比未序列化的对象占用更少的空间。
MEMORY_AND_DISK_SER(Java和Scala)把RDD分区以序列化的对象的形式存储在内存中。内存中存不下的对象存储到硬盘上。
DISK_ONLY仅把RDD分区存储在磁盘上。
MEMORY_ONLY_2\MEMORY_AND_DISK_2同上,但是在两个群集节点上复制每个分区。
OFF_HEAP把RDD分区以序列化的对象的形式存储在内存中。该选项要求使用堆外内存,仅供实验使用。

2、StorageLevel:StorageLevel(useDisk, useMemory, useOffHeap, deserialized, replication=1)

常量useDisk(使用硬盘)useMemory(使用内存)useOffHeap(使用堆外内存)Deserialized(未序列化)Replication(复制份数)
MEMORY_ONLYFalseTrueFalseTrue1
MEMORY_AND_DISKTrueTrueFalseTrue1
MEMORY_ONLY_SERFalseTrueFalseFalse1
MEMORY_AND_DISK_SERTrueTrueFalseFalse1
DISK_ONLYTrueFalseFalseFalse1
MEMORY_ONLY_2FalseTrueFalseTrue2
MEMORY_AND_DISK_2TrueTrueFalseTrue2
OFF_HEAPFalseFalseTrueFalse1

3、.getStorageLevel()
语法:rdd.getStorageLevel()
功能:.getStorageLevel()函数可查看RDD的存储级别。

4、RDD缓存:缓存RDD会把数据持久化到内存中,缓存不会溢写到磁盘,因为缓存只使用内存,使用存储级别MEMORY_ONLY持久化(rdd.persist(storageLevel=StorageLevel.MEMOLY_ONLY)等价于rdd.cache())。
持久化:在使用任意一种需要硬盘的存储选项时,持久化的分区会以本地文件的形式,存储在运行对应应用的Spark执行器的工作节点上。
#注:RDD持久化只会在有行动触发该RDD计算时才真正发生。如果没有足够的资源,持久化就不会发生(如内存不足)。

5、RDD持久化与解除持久化
持久化
①.persist()
语法:rdd.persist(storageLevel=StorageLevel.MEMORY_ONLY)
功能:.persist()方法指定了RDD所需的存储级别和存储属性。
以下两种持久化语句等价

from pyspark import StorageLevel

rdd.persist(StorageLevel.MEMORY_ONLY)
rdd.persitst(StorageLevel(False, True, False, True, 1))

解除持久化
②.unpersist()
语法:rdd.unpersist()
功能:.unpersist()方法解除指定的RDD持久化。
#注:若要改变一个已持久化RDD的存储选项,必须先解除该RDD的持久化。

6、保存检查点
保存检查点会把数据保存到文件中。基于磁盘的持久化会在Spark驱动器程序完成时删除持久化的RDD数据,而检查点保存的数据在应用结束后依然保存
#注:在行动操作RDD之前,就应请求保存该RDD的检查点。

7、检查点有关方法
①.setCheckpointDir()
语法:sc.setCheckpointDir(dirName)
功能:.setCheckpointDir()方法设置RDD检查点的保存目录。若在Hadoop集群上运行Spark,dirName参数指定的目录必须是HDFS路径

②.checkpoint()
语法:rdd.checkpoint()
功能:.checkpoint()方法把RDD标记为需要保存的检查点。在执行第一个用到该RDD的行动操作时,该RDD的检查点就会保存下来。
#注:.checkpoint()方法必须在任何行动操作请求该RDD之前调用。

③.isCheckpointed()
语法:rdd.isCheckpointed()
功能:.isCheckpointed()方法返回一个布尔值,表示该RDD是否被设置了检查点。

④.getCheckpointFile()
语法:rdd.getCheckpointFile()
功能:.isCheckpointed()方法返回RDD检查点所保存的文件的文件名。

8、使用外部程序处理RDD
想要在Spark程序中使用已有的而Python、Scala或Java中不存在的代码库,可通过.pipe()函数实现在Spark中使用外部程序。
①.pipe()
语法:rdd.pipe(command,env=None,checkCode=False)
功能:.pipe()方法返回一个RDD,它通过把输入RDD的元素通过“管道”传给command参数指定的外部进程,获取新RDD的对应元素。参数env是一个由环境变量组成的字典对象,参数checkCode指定是否检查shell命令的返回值。
#注:在运行转化操作.pipe()之前,需要执行sc.addFile(“文件路径”),将其分发到集群中所有参与执行的工作节点上。

9、使用Spark进行数据采样
①.sample()
语法:rdd.sample(withReplacement,fraction,seed=Node)
功能:.sample()是转换操作,根据原RDD整体数据集的百分比,创建出由采样数据子集组成的新RDD。参数withReplacement(布尔类型),指定RDD中的元素是否会被多次采样;参数fraction指定返回的采样结果RDD占原始数据的大致百分比;参数seed(整型)代表随机数生成器使用的随机种子。(.sampleByKey用来操作键值对RDD)。

②.takeSample()
语法:rdd.takeSample(withReplacement,num,seed=Node)
功能:.takeSample()是行动操作,从被采样的RDD中返回一个随机地值列表。参数num表示随机选择的返回记录的条数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值