被大量task使用的变量,使用广播。被广播的变量,在Driver中存在一个初始副本。这样,就不需要每个task都拥有一个变量的副本,节省网络传输的资源和内存的资源;每一个BlockManager有一个变量的副本,BlockManager中没有变量的时候,可以去Driver中获取,也可以从距离最近的其他BlockManager中获取。
设置广播变量:sc.broadcast();传入变量,如:
Broadcast
Map
默认情况下,Spark内部使用的是Java的序列化机制,ObjectOutputStream/ObjectInputStream,对象输入输出流机制,来进行序列化。
这种默认的序列化机制的好处在于,处理起来比较方便,也不需要我们手动做什么,只是在算子中使用的变量,必须实现Serializable接口,可序列化即可。缺点在于,默认的序列化机制效率不高,序列化的速度慢,序列化之后的数据,占用的空间相对还是比较大。。
Spark支持使用Kryo序列化机制,比默认的序列化机制速度快,占用空间小(官方说明,仅为默认序列化机制的1/10)。所以,用Kryo序列化机制进行序列化后,网络传输更快,在集群中占用内存资源更少。
1.算子函数中使用到的外部变量
2.持久化RDD是进行序列化,比如StorageLevel.MEMORY_ONLY_SER
3.shuffle
4.算子函数中使用到的外部变量,使用Kryo之后,优化网络传输的性能,可以优化集群中内存的占用和消耗
5.持久化RDD,优化内存占用和消耗;持久化RDD占用的内存越少,task执行的时候,创建的对象,就不至于频繁地沾满内存,发生GC
6.shuffle,可以优化网络传输的性能
在SparkConf中设置属性,spark.serializer,org.apache.spark.serializer.KryoSerializer类
注册使用到的自定义的类
Kryo没有被设置成默认的序列化机制,是因为要达到Kryo的最佳性能,必须要注册自定义的类。比如,算子函数中使用到了外部自定义类型的对象变量,必须注册使用到的类,否则Kryo达不到最佳性能。
项目中的使用:
.set("spark.serializer","org.apache.spark.serializer.KryoSerializer").registerKryoClasses(new Class[]{Student.class})
fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型的map、set、list和queue;
fastutil能够提供更小内存的占用,更快的存取速度;使用fastutil提供的集合类来替代自己平时使用的JDK原生的Map、List、Set,可以减少内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值的时候,提供更快的存取速度;
fastutil也提供了64位的array、set和list,以及高性能快速的,以及使用的IO类,来处理二进制和文本类型的文件;
fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实现了Java的Map接口),因此可以直接放入已有系统的任何代码中;
fastutil还提供了一些JDK标准类库中没有的额外的功能(比如双向迭代器);
fastutil除了对象和原始类型为元素的集合,也提供引用类型的支持,但是对引用类型是使用等于号(=)进行比较,而不是equals()方法;
fastutil尽量提供在任何场景下都是速度最快的集合类库;
1.如果算子函数使用了外部变量,首先可以使用Broadcast广播变量优化;其次可以使用Kryo序列化类库,提升序列化性能和效率;最后,如果外部变量是某种比较大的集合,可以考虑使用fastutil改写外部变量。首先从源头上减少内存的占用,通过广播变量进一步减少内存占用,再通过Kryo序列化类库进一步减少内存占用
2.在算子函数里,也就是task要执行的计算逻辑里,如果要创建比较大的Map、List集合,可能会占用较大的内存空间,而且可能涉及到消耗性能的遍历、存取等集合操作。这种情况下,可以考虑将这些集合类型使用fastutil类库重写,使用了fastutil集合类以后,就可以在一定程度上,减少task创建出来的集合类型的内存占用。避免executor内存频繁占满,频繁唤醒GC,导致性能下降
fastutil调优效果可能并没有我们想象中的好。举个例子,一个spark作业,在经过资源、并行度、RDD序列化的调优之后,30分钟可以运行完;那么再使用broadcast、Kryo和fastutil进行优化,可能29分钟运行完。所以效果可能不会像我们想象的那么好。
比如,List
Map比较特殊,如Int2IntMap,代表了key-value映射的元素类型。
除此之外,还支持object、reference等。
PROCESS_LOCAL:进程本地化,代码和数据在同一个进程executor中,计算数据的task有executor执行,数据和executor在同一个BlockManager中;性能最好
NODE_LOCAL:节点本地化,代码和数据在同一个节点中,但是计算数据的task和数据不在同一个executor中,数据需要在进程之间进行传输
NO_PREF:对于task来说,数据从哪里获取,对性能没有影响
RACK_LOCAL:机架本地化,数据和task在同一个机架的两个节点上;数据需要在节点之间通过网络进行传输
ANY:数据和task可能在集群中的任何地方,也可能不在同一个机架上,性能最差
观察日志,spark作业的运行日志。在测试的时候,先用client模式,在本地就可以直接看到比较全的日志。日志里面会显示,starting task...,PROCESS_LOCAL,NODE_LOCAL。观察大部分task的数据本地化级别。
如果大多数都是PEOCESS_LOCAL,则不用调节;
如果大部分级别都是NODE_LOCAL,ANY,那么最好调节一下数据本地化的等待时长;调节完,再次运行,观察日志,反复调节,观察大部分的task的本地化级别有没有提升,以及整个spark作业的 运行时间是否缩短;
切忌,本地化级别提升了,但是因为大量的等待时长,导致整个spark作业的执行时间增加,这样就得不偿失了。
spark.locality.wait,默认是3s。
默认情况下,下面3个等待时长,都是和spark.locality.wait相同的,都是3s:
spark.locality.wait.process
spark.locality.wait.node
spark.locality.wait.rack
设置代码:
new SparkConf()
.set("spark.locality.wait","6")
点赞 1
————————————————
版权声明:本文为CSDN博主「Johnson8702」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Johnson8702/article/details/86705817