概念:
序列化:将Java对象转化为字节数组
反序列化:将字节数组转化为Java对象
在RPC应用中,进行跨进程远程调用的时候,需要使用特定的序列化技术,需要对进行网络传输的对象进行序列化和反序列化。
影响序列化选择有两个因素:
- 序列化之后码流的大小,如果太大,那么将会影响网络传输的性能。
- 序列化和反序列化过程的性能
常用的序列化框架性能比较
序列化框架对比测试:
- JDK
- FastJson
- Hessian
- Hessian2
- Protostuff
准备
需要序列化的对象,这是一个复杂的对象。
NettyMessage
public class NettyMessage implements Serializable {
//消息头
private Header header;
//消息体
private Object body;
}
@Data
public class Header implements Serializable {
//校验头
private int crcCode;
//消息头消息体的总长度
private int length;
//全局唯一id
private long sessionId;
//消息类型
private MessageType type;
//扩展字段
private Map<String,Object> attachment;
}
@Data
public class RpcRequest implements Serializable {
private long requestId; //请求id
private String interfaceName; //调用类名
private String methodName; //调用方法名
private String[] parameterTypes; //方法参数类型
private Object[] parameters; //方法参数
}
public enum MessageType {
APP_RESPONE_TYPE;
}
创建一个构造器创建该对象。
public class NettyMessageBuilder {
public static NettyMessage build(){
NettyMessage message = new NettyMessage();
Header header = new Header();
RpcRequest request = new RpcRequest();
header.setCrcCode(1234);
header.setType(MessageType.APP_RESPONE_TYPE);
header.setLength(100);
header.setSessionId(200);
Map<String,Object> map = new LinkedHashMap<>();
map.put("demoKey",(Object)"demoValue");
header.setAttachment(map);
request.setInterfaceName("com.demo");
String[] types = {"java.lang.String" ,"java.lang.Integer"};
String[] param = {"java.lang.String" ,"java.lang.Integer"};
request.setParameterTypes(types);
request.setParameters(param);
request.setMethodName("buy");
request.setRequestId(123456);
message.setHeader(header);
message.setBody(request);
return message;
}
}
定义序列化接口
public abstract class AbstractSerialize {
public abstract <T> byte[] serialize(T obj);
public abstract <T> T deserialize(byte[] data, Class<T> clazz);
}
JDK
实现
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class JdkSerializeUtil extends AbstractSerialize {
@SneakyThrows
public <T> byte[] serialize(T obj) {
if (obj == null) {
throw new NullPointerException();
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
return bos.toByteArray();
}
@SneakyThrows
public <T> T deserialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
T obj = (T) ois.readObject();
return obj;
}
}
FastJson
引入pom
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
实现
import com.alibaba.fastjson.JSON;
public class FastjsonSerializeUtil extends AbstractSerialize {
public <T> byte[] serialize(T obj) {
if (obj == null){
throw new NullPointerException();
}
String json = JSON.toJSONString(obj);
byte[] data = json.getBytes();
return data;
}
public <T> T deserialize(byte[] data, Class<T> clazz) {
T obj = JSON.parseObject(new String(data),clazz);
return obj;
}
}
Hessian
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.60</version>
</dependency>
实现
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class HessianSerializeUtil extends AbstractSerialize {
@SneakyThrows
public <T> byte[] serialize(T obj) {
if (obj == null){
throw new NullPointerException();
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(bos);
ho.writeObject(obj);
return bos.toByteArray();
}
@SneakyThrows
public <T> T deserialize(byte[] data, Class<T> clazz) {
if (data == null){
throw new NullPointerException();
}
ByteArrayInputStream bis = new ByteArrayInputStream(data);
HessianInput hi = new HessianInput(bis);
return (T)hi.readObject();
}
}
Hessian2
实现:
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class Hessian2SerializeUtil extends AbstractSerialize {
@SneakyThrows
public <T> byte[] serialize(T obj) {
if (obj == null){
throw new NullPointerException();
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output ho = new Hessian2Output(bos);
ho.writeObject(obj);
ho.flush();
return bos.toByteArray();
}
@SneakyThrows
public <T> T deserialize(byte[] data, Class<T> clazz) {
if (data == null){
throw new NullPointerException();
}
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input hi = new Hessian2Input(bis);
return (T)hi.readObject();
}
}
Protostuff
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.6.0</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-runtime -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.6.0</version>
</dependency>
实现:
public class ProtostuffSerializeUtil extends AbstractSerialize {
/**
* 避免每次序列化都重新申请Buffer空间
*/
private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
/**
* 缓存Schema
*/
private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>();
public <T> byte[] serialize(T obj) {
if (obj == null){
throw new NullPointerException();
}
Class<T> clazz = (Class<T>) obj.getClass();
Schema<T> schema = getSchema(clazz);
byte[] data;
try {
data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} finally {
buffer.clear();
}
return data;
}
public <T> T deserialize(byte[] data, Class<T> clazz) {
Schema<T> schema = getSchema(clazz);
T obj = schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
}
private static <T> Schema<T> getSchema(Class<T> clazz) {
Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
if (schema == null) {
//这个schema通过RuntimeSchema进行懒创建并缓存
//所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
schema = RuntimeSchema.getSchema(clazz);
if (schema != null) {
schemaCache.put(clazz, schema);
}
}
return schema;
}
}
测试
测试方法
import com.lot.entity.NettyMessage;
import com.lot.entity.NettyMessageBuilder;
import com.lot.serialize.AbstractSerialize;
import com.lot.serialize.FastjsonSerializeUtil;
import com.lot.serialize.HessianSerializeUtil;
import com.lot.serialize.JdkSerializeUtil;
import com.lot.serialize.ProtostuffSerializeUtil;
public class Main {
public static void main(String[] args) {
//这里替换各种序列化实现类
// AbstractSerialize serialize = new JdkSerializeUtil();
// AbstractSerialize serialize = new FastjsonSerializeUtil();
// AbstractSerialize serialize = new HessianSerializeUtil();
AbstractSerialize serialize = new ProtostuffSerializeUtil();
NettyMessage message = NettyMessageBuilder.build();
TimeUtil timeUtil = new TimeUtil();
TimeUtil timeUtil1 = new TimeUtil();
NettyMessage result = null;
byte[] serByte = serialize.serialize(message);
System.out.println("字节长度:" + serByte.length);
result = serialize.deserialize(serByte,NettyMessage.class);
//这里设置测试次数
for(int i = 0; i< 100000; i++){
//timeUtil.init();
timeUtil.start();
serByte = serialize.serialize(message);
timeUtil.end();
//System.out.println("序列化时间:"+ timeUtil.getAvrTimeUs() + " Us");
timeUtil1.start();
result = serialize.deserialize(serByte,NettyMessage.class);
timeUtil1.end();
}
System.out.println("序列化时间:"+ timeUtil.getAvrTimeUs() + " Us");
System.out.println("反序列化时间:"+ timeUtil1.getAvrTimeUs() + " Us");
System.out.println("结果:" + result);
}
}
这里定义了一个TimeUtil类来计时
public class TimeUtil {
private long startTime;
private long endTime;
private long timeSum;
private long count;
public void init() {
timeSum = 0;
count = 0;
}
public void start() {
startTime = System.nanoTime();
}
public void end() {
endTime = System.nanoTime();
timeSum += (endTime - startTime);
count++;
}
public long getAvrTimeNs() {
return (timeSum / count);
}
public long getAvrTimeUs() {
return (timeSum / count) / 1000;
}
public long getAvrTimeMs() {
return (timeSum / count) / 1000000;
}
码流大小(byte) | 10次(us) | 100次(us) | 1000次(us) | 10000次(us) | 100000次(us) | |
---|---|---|---|---|---|---|
FastJson | 305 | 116-243 | 106-185 | 90-140 | 26-39 | 8-12 |
JDK | 866 | 383-777 | 502-1101 | 123-334 | 54-237 | 15-76 |
Hessian | 520 | 959-3836 | 376-567 | 191-329 | 99-161 | 30-47 |
Hessian2 | 398 | 132-152 | 142-144 | 33-35 | 12-13 | 6-6 |
Protostuff | 193 | 103-145 | 90-137 | 75-135 | 15-24 | 5-8 |
注:
- 码流单位为字节
- 序列化耗时-反序列化耗时,单位为微秒
从以上测试可以看出
- JDK方式的码流最大,不利于网络传输。
- 从整体来看,Prorostuff的码流最小,Prorostuff 和 Hessian2序列化性能最好。