2020年对我来说是忙碌的一年,2021年也毫无悬念的需要继续忙碌。因为公司从2020年从零开始构建云原生平台,从原有队伍中抽调了一部分人再加上新招聘的组成了目前的团队承接这项寄托或者影响着公司未来的产品。既然是云原生,微服务是必不可少的。缓存、队列自然也是必备套件。在redis存储及队列消息发送时都面临序列化框架的选择。
序列化框架对于上层应用来说是透明的。应用的开发同学无需关注或者甚至有些都压根不知道序列化的存在,但作为技术中台的同学,我们还是会面临序列化框架选择的问题。也许你会嘲笑我,“为什么要纠结那么一丁点的性能差异,难道Json序列化的可读性不香吗?”。但我觉得凡事都应该论个究竟,如果Json序列化那么香,为什么还会有这么多序列化框架的存在? 技术框架的性能也直接影响到所有应用的性能,所以在选择框架的时候除了本身框架的稳定性、维护性、扩展性之外,性能表现也会作为选择评比的重要参考指标。
Java生态的序列化框架有不少。除了Jdk序列化、Jackson、被安全问题饱受争议的FastJson之外,像Hessian、Kryo、Fst、Protostuff等序列化框架也被广泛应用。而针对这些框架本身的性能表现如何,还是要实测一把才能有个准确的对比。下面就针对Kryo、Fst、Protostuff进行下序列化、反序列化的性能测试。
先看一下要被序列化的对象。我定义了User、Group对象,应该也是我们在构建应用时设计的最简单的对象了。
@Data
public class User implements Serializable {
private Stringid;
private String name;
private Integer age;
private String desc;
private Group group;
}
User对象有个关联属性group,表示用户所属的分组或者部门。
@Data
public class Groupimplements Serializable {
private Stringid;
private Stringname;
private Stringdescription;
}
分别定义了三个序列化的类。
Kryo序列化类:
public class JanzKryoSerializerimplements Serializer{
private Kryokryo;
private volatile static Serializerserializer;
private JanzKryoSerializer(){
kryo =new Kryo();
kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer());
kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer());
kryo.register(Double.class, new DefaultSerializers.DoubleSerializer());
kryo.register(Float.class, new DefaultSerializers.FloatSerializer());
kryo.register(Byte.class, new DefaultSerializers.ByteSerializer());
kryo.register(Integer.class, new DefaultSerializers.IntSerializer());
kryo.register(Long.class, new DefaultSerializers.LongSerializer());
kryo.register(String.class, new DefaultSerializers.StringSerializer());
kryo.register(StringBuffer.class, new DefaultSerializers.StringBufferSerializer());
kryo.register(Date.class, new DefaultSerializers.DateSerializer());
// kryo.setRegistrationRequired(true);
}
public static SerializergetInstance(){
if(serializer ==null){
synchronized(JanzKryoSerializer.class){
serializer =new JanzKryoSerializer();
}
}
return serializer;
}
@Override
public byte[]serialize(T obj) {
Output output =null;
try {
ByteArrayOutputStream baos =new ByteArrayOutputStream();
output =new Output(baos);
kryo.writeClassAndObject(output, obj);
output.flush();
return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
}finally {
if(output !=null){
output.close();
}
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public T deserialize(byte[] bits, Class clazz) {
if(bits ==null || bits.length ==0){
return null;
}
Input ois =null;
try {
ByteArrayInputStream bais =new ByteArrayInputStream(bits);
ois =new Input(bais);
return (T)kryo.readClassAndObject(ois);
}catch(Exception e){
e.printStackTrace();
}finally {
if(ois !=null){
ois.close();
}
}
return null;
}
Fst序列化类:
class JanzFstSerializer implements Serializer{
private FSTConfiguration configuration;
private volatile static Serializer serializer;
private JanzFstSerializer() {
configuration = FSTConfiguration.createDefaultConfiguration();
}
public static Serializer getInstance() {
if (serializer == null) {
synchronized (JanzFstSerializer.class) {
serializer = new JanzFstSerializer();
}
}
return serializer;
}
@Override
public byte[] serialize(Object obj) {
return configuration.asByteArray(obj);
}
@Override
public <T> T deserialize(byte[] bits, Class<T> clazz) {
if (bits == null) {
return null;
}
return (T)configuration.asObject(bits);
}
}
Protostuff序列化类:
public class JanzStuffSerializer implements Serializer {
private volatile static Serializer serializer;
private JanzStuffSerializer() {
}
public static Serializer getInstance() {
if (serializer == null) {
synchronized (JanzStuffSerializer.class) {
serializer = new JanzStuffSerializer();
}
}
return serializer;
}
@Override
public <T> byte[] serialize(T obj) {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(clazz);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
try {
T obj = clazz.newInstance();
Schema<T> schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
private static <T> Schema<T> getSchema(Class<T> clazz) {
@SuppressWarnings("unchecked")
Schema<T> schema = (Schema<T>) cachedSchema.get(clazz);
if (schema == null) {
schema = RuntimeSchema.getSchema(clazz);
if (schema != null) {
cachedSchema.put(clazz, schema);
}
}
return schema;
}
}
基于定义的三种序列化类,编写测试方法:
public static void main(String[] args) {
int count = 100000;
StopWatch stopWatch = new StopWatch();
Group group = new Group();
group.setId("12123123123NBNBNBNBNBNB");
group.setName("北京致远互联股份有限公司");
group.setDescription("XXNB厂商, 坐标:北京市海淀区XX中心");
List<User> userList = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = new User();
user.setId("230028340823048203840823NBNBNBNBNBNB-" + i);
user.setAge(i);
user.setName("我是来自火星的超人-" + i);
user.setGroup(group);
user.setDesc("欢迎地球人到火星参观,顺便把我捎回去,我是火星人" + i);
userList.add(user);
}
JanzKryoSerializer.getInstance();
JanzFstSerializer.getInstance();
JanzStuffSerializer.getInstance();
stopWatch.start("kryo");
int size = 0;
for(User user : userList){
byte[] bytes = JanzKryoSerializer.getInstance().serialize(user);
size += bytes.length;
JanzKryoSerializer.getInstance().deserialize(bytes, User.class);
}
stopWatch.stop();
System.out.println("kryo size:" + size);
size = 0;
stopWatch.start("fst");
for(User user : userList){
byte[] bytes = JanzFstSerializer.getInstance().serialize(user);
size += bytes.length;
JanzFstSerializer.getInstance().deserialize(bytes, User.class);
}
stopWatch.stop();
System.out.println("fst size:" + size);
size = 0;
stopWatch.start("stuff");
for(User user : userList){
byte[] bytes = JanzStuffSerializer.getInstance().serialize(user);
size += bytes.length;
JanzStuffSerializer.getInstance().deserialize(bytes, User.class);
}
stopWatch.stop();
System.out.println("stuff size:" + size);
System.out.println(stopWatch.prettyPrint());
}
基于这样的测试输出结果如下:
kryo size:33058414
fst size:30800878
stuff size:27750158
StopWatch '': running time = 2388100212 ns
---------------------------------------------
ns % Task name
---------------------------------------------
1097995591 046% kryo
826917225 035% fst
463187396 019% stuff
从测试对比的结果来看,protostuff的性能优势还是很明显的。在没有其他条件影响的情况下,我们会采用protostuff作为默认的序列化器。
在当前的Java生态中,做任何事情都有很多的选择。本着求真的态度,我们不妨跑个demo,做个简单压测,除了熟悉框架的基本使用之外也跟具体的了解下组件的内部实现原理和实际表现。这样让我们在做选择的时候显得不会茫然失措。
实践是检验真理的唯一标准!