protobuf简单了解

@lucas  May 8, 2023

一、 protobufjprotobufbaidu/Jprotobuf-rpc-socket

1、 protobuf

xml、json是目前比较常用的数据交换格式,它们直接使用字段名称维护序列化后类实例中字段与数据之间的映射关系,一般用字符串的形式保存在序列化后的字节流中。消息和消息的定义相对独立,可读性较好。但序列化后的数据字节很大,序列化和反序列化的时间较长,数据传输效率不高;

protobuf是Google提供一个具有高效的协议数据交换格式工具库(类似Json)。 Protobuf和Xml、Json序列化的方式不同,采用了二进制字节的序列化方式。

Java中使用:

(1)添加maven依赖

        <!-- protobuf依赖  -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

(2)protobuf maven插件(不需要使用protoc工具去输入一串命令来解析.proto文件了)

<plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${plugin.protobuf.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                    <clearOutputDirectory>false</clearOutputDirectory>
                    <protocArtifact>
                        com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--                    <outputDirectory>${basedir}/src/test/java</outputDirectory>-->
                    <pluginId>grpc-java</pluginId>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

(3)创建.proto文件说明文件,定义消息体结构(${project.basedir}/src/main/proto下定义)

syntax = "proto3";
option java_package = "com.baidu.entity";
option java_outer_classname = "PersonModel";

message Person {
    int64 id = 1;
    string name = 2;
    string email = 3;
    repeated Phone phone = 4;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message Phone {
        string number = 1;
        PhoneType type = 2;
    }
}

(4)点击protobuf:compile,使用插件生成.proto对应的java类(读写接口)

(5)利用protobuf API进行序列化与反序化操作序列化操作:

public class ProtoBufTest {
    private static final Long NUM = 100000L;

    @Test
    public void test() throws InvalidProtocolBufferException {
        System.out.println("*****************  ProtoBuf测试 *******************");
        PersonModel.Person person = init();
        PersonModel.Person personOut = null;
        long old = System.currentTimeMillis();
        byte[] buff = null;
        for (Long i = 0L; i < NUM; i++) {
            buff = encode(person);
        }
        System.out.println("ProtoBuf 编码耗时:" + (System.currentTimeMillis() - old) + " ms");
        System.out.println(Arrays.toString(buff));
        System.out.println("ProtoBuf 数据长度:" + buff.length);
        System.out.println();
        System.out.println("-开始解码-");

        old = System.currentTimeMillis();

        for (Long i = 0L; i < NUM; i++) {
            personOut = decode(buff);
        }

        System.out.println("ProtoBuf 解码耗时:" + (System.currentTimeMillis() - old) + " ms");
        System.out.printf("Id:%d, Name:%s\n", personOut.getId(), personOut.getName());
        List<PersonModel.Person.Phone> phoneList = personOut.getPhoneList();
        for (PersonModel.Person.Phone phone : phoneList) {
            System.out.printf("手机号:%s (%s)\n", phone.getNumber(), phone.getType());
        }


        System.out.println();
        System.out.println("*****************  Json测试 *******************");
        PhoneInfo phoneInfo1 = new PhoneInfo();
        PhoneInfo phoneInfo2 = new PhoneInfo();
        phoneInfo1.setPhone("19982727185");
        phoneInfo1.setType(PhoneType.MOBILE);
        phoneInfo2.setPhone("10086");
        phoneInfo2.setType(PhoneType.HOME);
        Person zzg = Person.builder()
                      .id(1997L)
                .email("1783022886@qq.com")
                .phoneList(Lists.list(phoneInfo1, phoneInfo2))
                .name("zzg")
                .build();

        old = System.currentTimeMillis();
        String bytes = null;
        for (Long i = 0L; i < NUM; i++) {
            bytes = JSONObject.toJSONString(zzg);
        }
        System.out.println("Json 编码耗时:" + (System.currentTimeMillis() - old) + " ms");
        System.out.println(bytes);
        System.out.println("Json 字符串长度:" + bytes.length());
        System.out.println();
        System.out.println("-开始解码-");
        Person out = null;
        old = System.currentTimeMillis();
        for (Long i = 0L; i < NUM; i++) {
            out = JSONObject.parseObject(bytes, Person.class);
        }
        System.out.println("Json 解码耗时:" + (System.currentTimeMillis() - old) + " ms");
        System.out.printf("Id:%d, Name:%s\n", out.getId(), out.getName());
        List<PhoneInfo> phoneList1 = out.getPhoneList();
        for (PhoneInfo phone : phoneList1) {
            System.out.printf("手机号:%s (%s)\n", phone.getPhone(), phone.getType());
        }
    }

    private byte[] encode(PersonModel.Person person) {
        return person.toByteArray();
    }

    private PersonModel.Person decode(byte[] buff) throws InvalidProtocolBufferException {
        return PersonModel.Person.parseFrom(buff);
    }

    private PersonModel.Person init() {
        PersonModel.Person.Phone.Builder phoneBuider = PersonModel.Person.Phone.newBuilder();
        PersonModel.Person.Phone phone = phoneBuider
                .setNumber("19980007185")
                .setType(PersonModel.Person.PhoneType.MOBILE)
                .build();

        PersonModel.Person.Phone phone1 = phoneBuider
                .setNumber("10086")
                .setType(PersonModel.Person.PhoneType.HOME)
                .build();

        PersonModel.Person.Builder personBuilder = PersonModel.Person.newBuilder();

        PersonModel.Person person = personBuilder
                .setId(1997)
                .setEmail("zhouzg01@163.com")
                .addPhone(phone)
                .setName("zzg")
                .addPhone(phone1)
                .build();
        return person;
    }
}

(6)输出结果

  • NUM=1次:

    *****************  ProtoBuf测试 *******************
    ProtoBuf 编码耗时:51 ms
    [8, -51, 15, 18, 3, 122, 122, 103, 26, 22, 122, 104, 111, 117, 122, 104, 105, 103, 97, 111, 48, 49, 64, 98, 97, 105, 100, 117, 46, 99, 111, 109, 34, 13, 10, 11, 49, 57, 57, 56, 50, 55, 50, 55, 49, 56, 53, 34, 9, 10, 5, 49, 48, 48, 56, 54, 16, 1]
    ProtoBuf 数据长度:58
    
    -开始解码-
    ProtoBuf 解码耗时:6 ms
    Id:1997, Name:zzg
    手机号:19980027185 (MOBILE)
    手机号:10086 (HOME)
    
    *****************  Json测试 *******************
    Json 编码耗时:183 ms
    {"email":"1783000086@qq.com","id":1997,"name":"zzg","phoneList":[{"phone":"19980027185","type":"MOBILE"},{"phone":"10086","type":"HOME"}]}
    Json 字符串长度:138
    
    -开始解码-
    Json 解码耗时:40 ms
    Id:1997, Name:zzg
    手机号:19982727185 (MOBILE)
    手机号:10086 (HOME)
  • NUM=1000次:

    *****************  ProtoBuf测试 *******************
    ProtoBuf 编码耗时:81 ms
    [8, -51, 15, 18, 3, 122, 122, 103, 26, 22, 122, 104, 111, 117, 122, 104, 105, 103, 97, 111, 48, 49, 64, 98, 97, 105, 100, 117, 46, 99, 111, 109, 34, 13, 10, 11, 49, 57, 57, 56, 50, 55, 50, 55, 49, 56, 53, 34, 9, 10, 5, 49, 48, 48, 56, 54, 16, 1]
    ProtoBuf 数据长度:58
    
    -开始解码-
    ProtoBuf 解码耗时:23 ms
    Id:1997, Name:zzg
    手机号:19950007185 (MOBILE)
    手机号:10086 (HOME)
    
    *****************  Json测试 *******************
    Json 编码耗时:270 ms
    {"email":"1783000086@qq.com","id":1997,"name":"zzg","phoneList":[{"phone":"19980027185","type":"MOBILE"},{"phone":"10086","type":"HOME"}]}
    Json 字符串长度:138
    
    -开始解码-
    Json 解码耗时:74 ms
    Id:1997, Name:zzg
    手机号:19980027185 (MOBILE)
    手机号:10086 (HOME)
  • NUM=100000次:

    *****************  ProtoBuf测试 *******************
    ProtoBuf 编码耗时:182 ms
    [8, -51, 15, 18, 3, 122, 122, 103, 26, 22, 122, 104, 111, 117, 122, 104, 105, 103, 97, 111, 48, 49, 64, 98, 97, 105, 100, 117, 46, 99, 111, 109, 34, 13, 10, 11, 49, 57, 57, 56, 50, 55, 50, 55, 49, 56, 53, 34, 9, 10, 5, 49, 48, 48, 56, 54, 16, 1]
    ProtoBuf 数据长度:58
    
    
    - 开始解码 -
    ProtoBuf 解码耗时:201 ms
    Id:1997, Name:zzg
    手机号:19980027185 (MOBILE)
    手机号:10086 (HOME)
    
    *****************  Json测试 *******************
    Json 编码耗时:1086 ms
    {"email":"178300086@qq.com","id":1997,"name":"zzg","phoneList":[{"phone":"19982007185","type":"MOBILE"},{"phone":"10086","type":"HOME"}]}
    Json 字符串长度:138
    
    -开始解码-
    Json 解码耗时:325 ms
    Id:1997, Name:zzg
    手机号:19980027185 (MOBILE)
    手机号:10086 (HOME)

2、jprotobuf

jprotobuf是针对Java程序开发一套简易类库,目的是简化java语言对protobuf类库的使用
使用jprotobuf可以无需再去了解proto文件操作与语法,直接使用java注解定义字段类型即可。

jprotobuf工作原理如下:

  1. 扫描类上的注解的信息,进行分析(与protobuf读取proto文件进行分析过程相似)
  2. 根据注解分析的结果,动态生成java代码进行protobuf序列化与反序列化的功能实现
  3. 使用JDK6及以上的 code compile API进行编译后加载到classloader

使用教程:

(1) maven依赖包

    <dependency>
            <groupId>com.baidu</groupId>
            <artifactId>jprotobuf-precompile-plugin</artifactId>
            <version>2.2.5</version>
        </dependency>

(2)使用注解直接使用/创建POJO类

@Data
@ProtobufClass
public class Person {
    //@Protobuf(fieldType= FieldType.INT64, order=1)
    private Long id;
    //@Protobuf(fieldType= FieldType.STRING, order=2)
    private String name;
    //@Protobuf(fieldType= FieldType.STRING, order=3)
    private String email;
    //@Protobuf(fieldType= FieldType.OBJECT, order=4)
    private List<PhoneInfo> phoneList;
}


@Data
@ProtobufClass
public class PhoneInfo {
    //@Protobuf(fieldType= FieldType.STRING, order=1)
    private String phone;

    //@Protobuf(fieldType= FieldType.ENUM, order=2)
    private PhoneType type;
}

(3)使用jprotobuf API进行序列化与反序列化操作

public class JprotobufTest {
    @Test
    public void test() throws IOException {
        Codec<Person> personCodec = ProtobufProxy.create(Person.class);

        PhoneInfo phoneInfo1 = new PhoneInfo();
        PhoneInfo phoneInfo2 = new PhoneInfo();
        phoneInfo1.setPhone("19980027185");
        phoneInfo1.setType(PhoneType.MOBILE);
        phoneInfo2.setPhone("10086");
        phoneInfo2.setType(PhoneType.HOME);
      
        Person person = new Person();
        person.setEmail("1783000086@qq.com");
        person.setId(1997L);
        person.setName("zzg");
        person.setPhoneList(Lists.newArrayList(phoneInfo1, phoneInfo2));
      
        // 序列化
        byte[] bytes = personCodec.encode(person);
        System.out.println(Arrays.toString(bytes));
        // 反序列化
        Person decode = personCodec.decode(bytes);
        System.out.printf("Id:%d, Name:%s\n", decode.getId(), decode.getName());
        List<PhoneInfo> phoneList = decode.getPhoneList();
        for (PhoneInfo phone : phoneList) {
            System.out.printf("手机号:%s (%s)\n", phone.getPhone(), phone.getType());
        }
    }

3、baidu/Jprotobuf-rpc-socket

jprotobuf-rpc-http 是应用jprotobuf类库实现基于http协议的RPC开发组件。
现可支持直接把Google protobuf的IDL定义语言发布成RPC服务,客户端也可以直接应用IDL定义语言进行动态创建,帮助开发完全省去了手工编译protobuf IDL语言的麻烦。


添加新评论

  1. 666

    写的太好了

    Reply