如何使用grpc的序列化方式-protobuf来序列化java的object对象?结尾有反转

如何使用protobuf来序列化object对象?结尾有反转

protobuf-java 是google推出针对java语言版本的protobuf序列化sdk,也是grpc所采用的序列化方式,其耗时以及空间大小占用都优于大部分序列化,比如json,hessian。但是protobuf需要自己定义idl语句实现跨语言性,那有没有一个工具可以自动根据java类来生成idl的.proto文件呢?答案是有的,那就是Jprotobuf

文档:jprotobuf/Document.md at master · jhunters/jprotobuf (github.com)

Jprotobuf 使用

测试类,需要注意的是需要加上@ProtobufClass,以及无参构造和setter,getter

java 复制代码
@Data
@ProtobufClass
@AllArgsConstructor
public class HeartBeat implements Serializable {

    private String initiator;

    Long updateTime;

    public HeartBeat(){
        this.updateTime = new Date().getTime();
    }

    public HeartBeat(String initiator){
        this.initiator = initiator;
        this.updateTime = new Date().getTime();
    }
}

测试代码

java 复制代码
@Test
public void testSerialization() throws IOException {
    Codec<HeartBeat> heartBeatCodec = ProtobufProxy.create(HeartBeat.class);
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = heartBeatCodec.encode(heartBeat);
    // 反序列化
    HeartBeat decode = heartBeatCodec.decode(encode);
    Assert.assertEquals(heartBeat, decode);
}

结果当然没有任何异常

log 复制代码
16:49:12.676 [main] DEBUG com.baidu.bjf.remoting.protobuf.ProtobufProxy - Field 'initiator' from com.rpc.domain.pojo.HeartBeat with @Protobuf annotation but not set order or order is 0, It will set order value to 1
16:49:12.682 [main] DEBUG com.baidu.bjf.remoting.protobuf.ProtobufProxy - Field 'updateTime' from com.rpc.domain.pojo.HeartBeat with @Protobuf annotation but not set order or order is 0, It will set order value to 2
16:49:12.797 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - Begin to compile source code: class is 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.395 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - compile source code done: class is 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.395 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class 'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'
16:49:14.396 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class done  'com.rpc.domain.pojo.HeartBeat$$JProtoBufClass'

Process finished with exit code 0

使用Jprotobuf来序列化Object会怎样

java 复制代码
@Test
public void testSerialization() throws IOException {
    Codec<Object> heartBeatCodec = ProtobufProxy.create(Object.class);
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = heartBeatCodec.encode(heartBeat);
    // 反序列化
    HeartBeat decode = (HeartBeat) heartBeatCodec.decode(encode);
    Assert.assertEquals(heartBeat, decode);
}

结果

kotlin 复制代码
java.lang.IllegalArgumentException: Invalid class [java.lang.Object] no field use annotation @com.baidu.bjf.remoting.protobuf.annotation.Protobuf at class java.lang.Object

at com.baidu.bjf.remoting.protobuf.utils.ProtobufProxyUtils.fetchFieldInfos(ProtobufProxyUtils.java:121)
at com.baidu.bjf.remoting.protobuf.code.AbstractCodeGenerator.<init>(AbstractCodeGenerator.java:75)
at com.baidu.bjf.remoting.protobuf.code.TemplateCodeGenerator.<init>(TemplateCodeGenerator.java:87)
at com.baidu.bjf.remoting.protobuf.ProtobufProxy.getCodeGenerator(ProtobufProxy.java:389)
....

这里报错的原因是没有使用@ProtobufClass注解,但是根本原因是因为使用protobuf是需要严格定义字段类型,Jprotobuf也只是根据你的类的字段属性来自动生成类型,所以java的Object是对应不上idl中的类型的,所以无法解析。

那怎么才能实现序列化object呢

也是有解决办法的。在protobuf-java 3.4.3 版本后支持了Any类型,同时最新的Jprotobuf也支持Any了。

以下是使用Any类型来实现Object序列化,封装一个公共序列化-反序列化工具类

java 复制代码
public class ProtobufSerialization implements RpcSerialization {
    @Override
    public <T> byte[] serialize(T obj) throws IOException {
        Any any = Any.pack(obj);
        Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
        return anyCodec.encode(any);
    }
    @Override
    public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
        Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
        anyCodec.decode(data);
        Any any = anyCodec.decode(data);
        String clazzName = any.getCodecClass().split("/")[1];
        try {
            Class<?> clazz = Class.forName(clazzName);
            if (any.is(clazz)) {
                Object res = any.unpack(clazz);
                return (T) res;
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}

使用Any.pack 和unpack就可以完成对Object类型的封装,是不是非常简单方便呢。同时根据Any对象中包含的全类名我们就可以通过反射来获取字节码。

但是使用是有一定条件的,也就是你的Object必须加上@ProtobufClass,以及无参构造和setter,getter。同时对于java提供的类比如List,Map,因为类上并没有@ProtobufClass,无参构造 ,所以你需要有个包装类来对其进行包装,就像MVC中使用@RequestBody 需要有类来包装基本数据类型一样。ps(包装类中的List,Map的范型必须指定特定类型而不能使用Object或者不写并且要与实际的一致)

Jprotobuf 工具类测试使用

ini 复制代码
@Test
public void testSerialization() throws IOException {
    ProtobufSerialization protobufSerialization = new ProtobufSerialization();
    HeartBeat heartBeat = new HeartBeat("test");
    // 序列化
    byte[] encode = protobufSerialization.serialize(heartBeat);
    // 反序列化
    HeartBeat decode = (HeartBeat) protobufSerialization.deserialize(encode, Object.class);
    System.out.println(decode);
}

非常Amazing啊!对象成功输出了。

bash 复制代码
...
17:12:09.152 [main] DEBUG com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler - loading class done  'com.baidu.bjf.remoting.protobuf.Any$$JProtoBufClass'
HeartBeat(initiator=test, updateTime=1708679527671)

Process finished with exit code 0

你以为这就完了?众所周知,Protobuf的性能是非常强的,不做个简单测试怎么能行。

工具性能测试

ini 复制代码
 @Test
    public void testSerialization() throws IOException {
        HeartBeat heartBeat = new HeartBeat("test");
        ProtobufSerialization protobufSerialization = new ProtobufSerialization();
        HessianSerialization hessianSerialization = new HessianSerialization();

        Long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            byte[] bytes = protobufSerialization.serialize(heartBeat);
            HeartBeat deserialize = (HeartBeat) protobufSerialization.deserialize(bytes, Object.class);
        }
        log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            byte[] bytes = hessianSerialization.serialize(heartBeat);
            HeartBeat deserialize = (HeartBeat) hessianSerialization.deserialize(bytes, Object.class);
        }
        log.info("hessian序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);

    }

结果我麻了

ini 复制代码
17:25:50.135 [main] INFO Test1 - protobuf序列化和反序列化耗时:2313
17:25:50.298 [main] INFO Test1 - hessian序列化和反序列化耗时:163

是的,你没看错,hessian序列化整整比protobuf快了10倍多,说好的性能呢?我转念一想,会不会是反射导致的瓶颈。于是乎改了下代码

ini 复制代码
@Override
public <T> T deserialize(byte[] data, Class<T> clz) throws IOException {
    Codec<Any> anyCodec = ProtobufProxy.create(Any.class);
    anyCodec.decode(data);
    Any any = anyCodec.decode(data);
    String clazzName = any.getCodecClass().split("/")[1];
    Object res = any.unpack(HeartBeat.class);
    return (T) res;
}

结果测下来,好了一点,只比hessian序列化慢10倍了。

ini 复制代码
17:30:45.056 [main] INFO Test1 - protobuf序列化和反序列化耗时:1964
17:30:45.247 [main] INFO Test1 - hessian序列化和反序列化耗时:190

但是这还能用?我又想是不是Any的问题,于是乎又改了下测试代码,不再使用工具了,而是指定类型

java 复制代码
@Test
public void testSerialization() throws IOException {
    HeartBeat heartBeat = new HeartBeat("test");
    ProtobufSerialization protobufSerialization = new ProtobufSerialization();
    HessianSerialization hessianSerialization = new HessianSerialization();
    JsonSerialization jsonSerialization = new JsonSerialization();
     
    Codec<HeartBeat> codec = ProtobufProxy.create(HeartBeat.class);
    
    Long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        byte[] bytes = codec.encode(heartBeat);
        HeartBeat deserialize = codec.decode(bytes);
    }
    log.info("protobuf序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        byte[] bytes = hessianSerialization.serialize(heartBeat);
        HeartBeat deserialize = (HeartBeat) hessianSerialization.deserialize(bytes, HeartBeat.class);
    }
    log.info("hessian序列化和反序列化耗时:{}", System.currentTimeMillis() - startTime);
}

结果

ini 复制代码
17:34:06.221 [main] INFO Test1 - protobuf序列化和反序列化耗时:57
17:34:06.341 [main] INFO Test1 - hessian序列化和反序列化耗时:118

破案了,就是Any导致的性能问题,所以想用Protobuf来序列化Object的想法还是洗洗睡吧.

相关推荐
一只叫煤球的猫19 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz96519 小时前
tcp/ip 中的多路复用
后端
bobz96519 小时前
tls ingress 简单记录
后端
皮皮林55120 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友21 小时前
什么是OpenSSL
后端·安全·程序员
bobz96521 小时前
mcp 直接操作浏览器
后端
前端小张同学1 天前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook1 天前
Manim实现闪光轨迹特效
后端·python·动效
武子康1 天前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在1 天前
6个值得收藏的.NET ORM 框架
前端·后端·.net