本文基于Spark2.1.0版本
套用官文Tuning Spark中的一句话作为文章的标题:*Often, choose a serialization type will be the first thing you should tune to optimize a Spark application. *
在Spark的架构中,在网络中传递的或者缓存在内存、硬盘中的对象需要进行序列化操作,序列化的作用主要是利用时间换空间:分发给Executor上的Task
需要缓存的RDD(前提是使用序列化方式缓存)
广播变量
Shuffle过程中的数据缓存
使用receiver方式接收的流数据缓存
算子函数中使用的外部变量
上面的六种数据,通过Java序列化(默认的序列化方式)形成一个二进制字节数组,大大减少了数据在内存、硬盘中占用的空间,减少了网络数据传输的开销,并且可以精确的推测内存使用情况,降低GC频率。
其好处很多,但是缺陷也很明显:把数据序列化为字节数组、把字节数组反序列化为对象的操作,是会消耗CPU、延长作业时间的,从而降低了Spark的性能。
至少默认的Java序列化方式在这方面是不尽如人意的。Java序列化很灵活但性能较差,同时序列化后占用的字节数也较多。
所以官方也推荐尽量使用Kryo的序列化库(版本2)。官文介绍,Kryo序列化机制比Java序列化机制性能提高10倍左右,Spark之所以没有默认使用Kryo作为序列化类库,是因为它不支持所有对象的序列化,同时Kryo需要用户在使用前注册需要序列化的类型,不够方便。
由于 Spark2.1.0默认对Task使用Java序列化(该序列化方式不允许修改,源码如下),/**
* Helper method to create a SparkEnv for a driver or an executor.
*/
private def create(
conf: SparkConf, executorId: String, bindAddress: String, advertiseAddress: String, port: Int, isLocal: Boolean, numUsableCores: Int, ioEncryptionKey: Option[Array[Byte]], listenerBus: LiveListenerBus = null, mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None): SparkEnv = {
val isDriver = executorId == SparkContext.DRIVER_IDENTIFIER
...
val serializer = instantiateClassFromConf[Serializer]( "spark.serializer", "org.apache.spark.serializer.JavaSerializer")
logDebug(s"Using serializer: ${serializer.getClass}")
val serializerManager = new SerializerManager(serializer, conf, ioEncryptionKey)
val closureSerializer = new JavaSerializer(conf) --Task闭包函数使用Java序列化库
所以本文主要针对下面这五种数据类型:需要缓存的RDD(前提是使用序列化方式缓存)
广播变量
Shuffle过程中的数据缓存
使用receiver方式接收的流数据缓存
算子函数中使用的外部变量
其实从Spark 2.0.0版本开始,简单类型、简单类型数组、字符串类型的Shuffling RDDs 已经默认使用Kryo序列化方式了。
下面,我给出具体的流程,来切换到Kryo序列化库。
先介绍几个相关的配置:Property NameDefaultMeaningspark.serializerorg.apache.spark.serializer.JavaSerializerClass to use for serializing objects that will be sent over the network or need to be cached in serialized form. The default of Java serialization works with any Serializable Java object but is quite slow, so we recomme