如何使用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的想法还是洗洗睡吧.

相关推荐
想躺平的小农21 分钟前
EasyExcel详解
java
慧一居士22 分钟前
EasyExcel集成使用总结与完整示例
java·excel
呦呦彬22 分钟前
【问题排查】easyexcel日志打印Empty row!
java·开发语言·log4j
九章云极AladdinEdu39 分钟前
GPU与NPU异构计算任务划分算法研究:基于强化学习的Transformer负载均衡实践
java·开发语言·人工智能·深度学习·测试工具·负载均衡·transformer
佩奇的技术笔记42 分钟前
Java学习手册:客户端负载均衡
java·负载均衡
可乐加.糖1 小时前
项目版本管理和Git分支管理方案
java·git·目标跟踪·gitlab·敏捷流程·源代码管理
wowocpp2 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go2 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf2 小时前
go语言学习进阶
后端·学习·golang
繁依Fanyi2 小时前
我的 PDF 工具箱:CodeBuddy 打造 PDFMagician 的全过程记录
java·pdf·uni-app·生活·harmonyos·codebuddy首席试玩官