kryo序列化测试
测试spark程序运行中对RDD进行操作,添加与不添加序列化在性能上的区别。
区别包括占用内存大小,程序运行时间等。
测试spark流程
随机生成字符串,以空格分割成行,进行多次map遍历。对结果进行持久化,并保存成文件。
case class DataCase(input: Int) // 一个简单的case class.
val testNew2 = testRDD.flatMap(x => x.split(" "))
.map(x => DataCase(x))
.map(x => x) // map * 10086
testNew2.persist()
testNew2.repartition(1)
.saveAsTextFile("./test.file")
persist() 方法包含了多种持久化类型。
persist() 默认持久化类型为org.apache.spark.storage.StorageLevel.MEMORY_ONLY
// persist(StorageLevel.MEMORY_ONLY)
持久化类型:
MEMORY_ONLY : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够,部分数据分区将不再缓存,在每次需要用到这些数据时重新进行计算。这是默认的级别。
MEMORY_AND_DISK : 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够,将未缓存的数据分区存储到磁盘,在需要使用这些分区时从磁盘读取。
MEMORY_ONLY_SER : 将 RDD 以序列化的 Java 对象的形式进行存储(每个分区为一个 byte 数组)。这种方式会比反序列化对象的方式节省很多空间,尤其是在使用 fast serializer时会节省更多的空间,但是在读取时会增加 CPU 的计算负担。
MEMORY_AND_DISK_SER : 类似于 MEMORY_ONLY_SER ,但是溢出的分区会存储到磁盘,而不是在用到它们时重新计算。
DISK_ONLY : 只在磁盘上缓存 RDD。
MEMORY_ONLY_2,MEMORY_AND_DISK_2,等等 : 与上面的级别功能相同,只不过每个分区在集群中两个节点上建立副本
另一个持久化方法 cache()
cache实际上调用persist(MEMORY_ONLY),等同于persist(MEMORY_ONLY)
序列化方案:使用kryo进行序列化或者使用java原生java.io.Serializable序列化。
使用序列化时,persist级别要使用MOMORY_ONLY_SER或者MEMORY_AND_DISK_SER。
测试数据:
800万条随机random 13位数字。生成的长字符串。以空格分隔开。106MB。
1000万条随机random 13位数据。生成的长字符串。以空格分割开。133MB
测试方案:
1 不使用kryo序列化,持久化使用默认的MEMORY_ONLY
2 不使用kryo序列化,持久化使用MEMORY_ONLY_SER
3 kryo序列化,不注册任何类,持久化使用MEMORY_ONLY_SER
4 kryo序列化,注册所有类(包括调用map时生成的匿名类),持久化使用MEMORY_ONLY_SER
5 kryo序列化,注册除匿名类外其他类,持久化使用MEMORY_ONLY_SER
测试结果:
1
文件大小106MB input 231.9MB 运行时间14s/15s/15s 持久化占用内存694.1MB
文件大小133MB input 342.4MB 运行时间33s 持久化占用内存(超出设置内存,持久化失败。)
2
文件大小106MB input 231.9MB 运行时间18s/19s 持久化占用内存171.1MB
文件大小133MB input 342.4MB 运行时间 30s 持久化占用内存213.8MB
3
文件大小106MB input231.9MB 运行时间15s/14s/15s 持久化占用内存 228.1MB
文件大小133MB input342.4MB 运行时间23s 持久化占用内存285.1MB
4
文件大小106MB input231.9MB 运行时间11s/12s/12s 持久化占用内存121.2MB
文件大小133MB input342.4MB 运行时间21s/20s/20s 持久化占用内存151.5MB
5
文件大小106MB input231.9MB 运行时间11s/12s/11s 持久化占用内存121.2MB
文件大小133MB input342.4MB 运行时间20s/19s/19s 持久化占用内存151.5MB
测试结论:
1. 不使用任何序列化,使用默认内存持久化占用较大内存。
2. 使用java自带的序列化,占用内存减少很多,但是运行时间最长。
3. 使用kryo序列化,不注册需要的类,占用内存比2要增加一些,运行时间和1一样。
4. 使用kryo序列化,注册所有类,占用内存最少,运行时间相比2有很大提升,相比1有部分提升。
5. 使用kryo序列化,注册主要的类,不注册匿名类,得到的结果与4基本相符。实际操作可以不注册匿名类,避免大量重复代码。
注册匿名类方法:
conf.registerKryoClasses(
Array(
Class.forName("test.TestRDD$$anonfun$1")
)
)
val temp = testRDD.flatMap(x => x.split(" "))
注册泛型类方法:
conf.registerKryoClasses(
Array(
classOf[test.TestClass[_]]
)
)
class TestClass[Int]() {...}