当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。
使用场景
在rdd的每一个分区上,执行迭代操作,在每一次的迭代操作中,需要先访问redis缓存,并获取key对应的value,若value存在则对value进行反序列化操作,否则从db里查询并序列化存放到redis缓存中。
伪代码如下:
rdd.mapPartitions {
iter.map{
val value:Option[Array[Byte]] = getValueByKey(key)
value match {
case Some(bs) => {
deserilialize(bs);
other operations...
}
case None => {
val newVals = fetchFromDbByKey(key);
other operations ....;
val newBs = serialize(key);
storeRedis(newBs)
}
}
}
}
从这段位代码可以看出,影响效率的有序列化和反序列化的效率以及序列化后byte数组的字节大小(可以影响网络IO)。
测试指标
主要从四方面来考虑,序列化总时间,反序列化总时间,序列化后平均字节大小,cpu使用率峰值。
其中,使用jconsole监控其cpu使用率峰值。
注意,cpu使用率的峰值只是一个参考,因为在数据量增大时,在序列化和反序列化过程中,伴随着gc,也会消耗cpu资源。
测试数据
https://github.com/Devskiller/jfairy.git 是用来生成测试数据的,可以支持多国语言,由于其本身不是用来测试序列化的数据集,其生成的对象也不是完全可序列化的,字段也多,也包含了一下二级字段,故简化之。
简化之后的数据结构如下:
生成测试数据代码如下:
package com.wisers;
import com.devskiller.jfairy.Fairy;
import com.devskiller.jfairy.producer.person.Person;
import java.util.ArrayList;
import java.util.Locale;
public class DataGenerator {
public static ArrayList<People> generatePeople(int sampleNum) {
Fairy chineseFairy = Fairy.create(Locale.CHINESE);
Fairy englishFairy = Fairy.create(Locale.ENGLISH);
ArrayList<People> people = new ArrayList<People>(sampleNum);
for (int i = 0; i < sampleNum; i++) {
Person person = Math.random() >= 0.5 ? chineseFairy.person() : englishFairy.person();
people.add(People.createBy(person));
}
return people;
}
}
测试环境
测试方案
尽可能地重用流对象,避免新创建对象对结果的影响
尽可能地避免gc对序列化和反序列化的影响,每次序列化反序列化之后都手动gc,并且测试数据集不宜过大,目前设定最大为1kw,尽可能避免gc对结果的影响
在测试操作过程中,避免打印以及磁盘读取存放等io操作,序列化后的数据直接放在内存,供反序列化使用。
测试结果
下面开始对比业内的比较认可的几种序列化方案。
序列化方案对比结果如下:
不同数据集下各个序列化方案对比
对比结果如下:
结果分析
时间角度分析
- 由于jdk本身在序列化和反序列化时,ObjectOutputStream、ByteArrayInputStream以及ObjectInputStream不能复用,序列化时间会包含部分对象创建的时间,这会增加gc时间
- msgpack在序列化过程中,MessageUnpacker不能复用,并且需要手动创建类,反序列化时间会比序列化时间长
- hession在反序列化过程中,流不能复用,反序列化时间会比较长
- kryo、fst、protoStuff 在序列化和反序列化的过程中,很好的使用了流复用,序列化效果比较好
- 整体来看,同一种序列化方案,反序列化消耗时间会比序列化消耗时间长,多了对象的创建以及字段映射时间
- 数量级达到百万级后,使用protoStuff、fst以及kryo效果比较好,整体时间消耗依次为 protoStuff < kryo < fst
序列化后字节大小分析
- 整体大小如下:msgpack < protoStuff < kryo < fst < hession < jdk
- msgpack需要手动序列化字段,并不包含类信息,故序列化后的结果比较小
最大堆内存对序列化时间的影响
默认最大堆内存约为7.7g,由于序列化后的数据被存放在内存,不能被gc回收,数据量达到1kw 时,出现内存溢出异常,故调大堆内存,对比在1kw 数据量时最大堆内存对序列化和反序列化的影响。
结果分析
当数据量在 1kw时,增大最大堆内存,对不能使用流复用技术的 jdk、hession影响比较大,影响为几秒,但整体时间影响并不大,对于其他序列化方案影响在毫秒级,几乎不影响。
分析总结
综合考虑序列化和反序列化时间以及序列化后的大小来看,优先使用 protoStuff 、 kryo 以及 fst 。