使用ProtoBuf实现类似JSON的操作:节省60%空间并提升100%性能的实用指南

引言

本文将介绍一种新的JAVA对象序列化工具——ProtoBuf(Protocol Buffers),它是由谷歌开发的一种高效、灵活且语言中立的结构化数据序列化协议。ProtoBuf被广泛应用于数据通信、数据存储和RPC(远程过程调用)等场景,尤其是在分布式系统和微服务架构中。经过ProtoBuf序列化后,数据体积通常显著小于JSON或XML,且占用更少的带宽和存储空间,同时解析速度更快。然而,相较于JSON和XML等文本格式,ProtoBuf的可读性较差,使用起来也相对复杂,主要是增加了开发和编译步骤(需要使用protoc编译.proto文件)。本篇将介绍如何使用JProtoBuf工具,以类似JSON的方式便捷地使用ProtoBuf。

快速入门

JProtobuf是一个基于Java的Protobuf序列化与反序列化工具,由百度开发并开源,旨在降低Protobuf在Java项目中的使用门槛。JProtobuf为Java开发者提供了一种更直观的方式来定义和操作Protobuf数据,无需使用.proto文件和编译工具,项目的GitHub地址如下:

JProtobuf: https://github.com/jhunters/jprotobuf

使用JProtobuf仅需三个简单步骤,即可完成JAVA对象与Protobuf数据之间的互相转换:

  • 第一步,添加Maven依赖:
<dependency>  
    <groupId>com.baidu</groupId>  
    <artifactId>jprotobuf</artifactId>  
    <version>2.4.23</version>  
</dependency>  
  • 第二步,为对象添加@ProtobufClass注解:
@Data  
@ProtobufClass  
public class User {  
    private Long id;  
    private String name;  
    private String trueName;  
    private Integer age;  
    private String sex;  
    private Date createTime;  
}  
  • 第三步,创建ProtobufProxy代理并进行使用:
User user = new User();  
user.setId(1L);  
user.setName("赵侠客");  
user.setAge(29);  
user.setSex("男");  
user.setTrueName("公众号");  
user.setCreateTime(new Date());  
// 创建JProtobuf代理  
Codec<User> codec = ProtobufProxy.create(User.class);  
// 使用Protobuf序列化  
byte[] bytes = codec.encode(user);  
System.out.println(bytes.length); // 38  
// 使用Protobuf反序列化  
User user1 = codec.decode(bytes);  

从上述代码可以看出,使用JProtobuf进行对象的序列化与JSON的序列化同样简单,经过Protobuf序列化后,字节长度为38,而JSON的字节长度为98,节省了61%。当然,并非所有对象在使用Protobuf后都能有如此显著的差距,接下来我们将比较中JSON大JSON的序列化后大小差异。

大小对比

3.1 中JSON大小对比

在此部分,我们参考前文《FastJson、Jackson、Gson、Hutool,JSON解析哪家强?JMH基准测试来排行》对小JSON中JSON大JSON的定义。我们将中JSON定义为一个包含20个用户对象的数组,由于JProtobuf不直接支持List对象,因此需将其封装为一个Users类:

@Data  
@ProtobufClass  
public class Users {  
    List<User> users;  
    private Long id;  
}  

使用上述20个用户对象创建中JSON

// 20个用户模拟中JSON  
List<User> userList = new ArrayList<>();  
IntStream.range(0, 20).forEach(i -> {  
    User user2=new User();  
    BeanUtil.copyProperties(user,user2);  
    userList.add(user2);  
});  
users.setUsers(userList);  

接下来,我们使用JProtobuf中JSON进行序列化:

Codec<Users> userListCodec = ProtobufProxy.create(Users.class);  
String mediumJson = JSON.toJSONString(users);  
byte[] mediumProtobuf = userListCodec.encode(users);  
mediumJson.getBytes().length + "\t" + mediumProtobuf.length;  
// 1991 800  

中JSON的序列化中,Protobuf序列化后的长度为800,JSON序列化后的长度为1991,Protobuf相较于JSON小了60%。

3.2 大JSON大小对比

大JSON在这里定义为稿件正文的富文本HTML数据:

@Data  
@ProtobufClass  
public class Article {  
    private Long id;  
    private String author;  
    private Long tenantId;  
    private String title;  
    private String subTitle;  
    private String htmlContent;  
    private Date publishTime;  
}  

我们将模拟大JSON数据:

// 稿件正文模拟大JSON  
article.setId(10000L);  
article.setTenantId(10000L);  
article.setAuthor("公众号:赵侠客");  
article.setPublishTime(new Date());  
article.setTitle(RandomUtil.randomString("主标题", 100));  
article.setSubTitle(RandomUtil.randomString("副标题", 50));  
// 公众号文章字符串长度为89544  
article.setHtmlContent(new String(Files.readAllBytes(Paths.get("article.html"))));  

使用JProtobuf对大JSON进行序列化:

Codec<Article> articleCodec = ProtobufProxy.create(Article.class);  
String bigJson = JSON.toJSONString(article);  
byte[] bigProtobuf= articleCodec.encode(article);  
bigJson.getBytes().length + "\t" + bigProtobuf.length;  
// 94595 92826  

在大JSON的情况下,Protobuf序列化的尺寸仅比JSON小2%,这可能是由于大数据量中的冗余信息占总信息大小极小,因此大数据量的JSON和Protobuf之间的差距不大。

性能对比

性能对比方面,我们参考了前面的文章《FastJson、Jackson、Gson、Hutool,JSON解析哪家强?JMH基准测试来排行,为了确保准确性,我们使用JMH基准测试,从小JSON中JSON大JSON的序列化与反序列化六项指标进行基准测试,并将性能最突出的FastJson2与Protobuf进行对比。

百分制:我们以FastJson2的分数为100分作为参考,比FastJson2快的得分大于100分。

缩写定义:

  • SS:小JSON序列化得分
  • MS:中JSON序列化得分
  • BS:大JSON序列化得分
  • SDS:小JSON反序列化得分
  • MDS:中JSON反序列化得分
  • BDS:大JSON反序列化得分
  • 变化:相对序列化得分变化

4.1 小JSON序列化

工具得分百分制
FastJson213561505100
Protobuf1285853294.8

可以看到,Protobuf在小对象序列化方面的性能已经接近FastJson2,表现非常优异。

4.2 中JSON序列化

工具得分百分制
FastJson2825644100
Protobuf43663552.9

在中JSON序列化中,Protobuf的性能约为FastJson2的一半,这可能与JProtobuf不直接支持List对象有关,因此性能未能表现得那么突出。

4.3 大JSON序列化

工具得分百分制
FastJson210086100
Protobuf21764215.8

Protobuf在大对象序列化中表现优异,其性能超过FastJson2一倍以上,远超其他JSON工具。

4.4 小JSON反序列化

工具百分制变化SDSSS
FastJson2100-41.7%792106913561505
Protobuf185.3+14.1%1467671212858532

在小对象的反序列化中,Protobuf表现出色,得分达到185.3,几乎是FastJson2的两倍,且性能比序列化时还要优越。

4.5 中JSON反序列化

工具百分制变化MDSMS
FastJson2100-53.3%385392825644
Protobuf79.4-29.9%306087436635

在数组对象的反序列化中,Protobuf的性能依然未能达到理想状态,表现不如FastJson2。

4.6 大JSON反序列化

工具百分制变化BDSBS
FastJson2100-35.7%648710086
Protobuf480.4+43.2%3116321764

在大对象的反序列化中,Protobuf表现突出,得分高达480.4,显示出其在处理大数据量时的优势,远超FastJson2。

总结

空间

以下是Protobuf相较于JSON在空间占用上的变化:

对象相比JSON
小JSON-61%
中JSON-60%
大JSON-2%

时间

Protobuf与JSON工具性能排名如下:

工具排名总分百分制SSMSBSSDSMDSBDS
Protobuf王者1108.6195.594.852.9215.8185.379.4480.4
FastJson2状元56710010010072.010010095.0
FastJson榜眼394.269.562.373.235.851.371.6100
Jackson探花34260.342.389.710027.431.351.3
Gson进士188.233.28.921.543.620.725.368.2
Hutool孙山42.27.43.24.67.77.35.513.9

图片

结论

经过上述测试,我们可以得出Protobuf的以下结论:

  • 在小数据情况下,Protobuf序列化的空间节省显著,测试中达到60%左右。
  • 在大数据情况下,Protobuf的序列化空间与JSON相差无几。
  • 小数据的序列化性能媲美FastJson2。
  • 在数组的序列化和反序列化性能上,Protobuf整体表现较低。
  • 在大数据的序列化和反序列化性能上,Protobuf明显优于所有JSON工具。

综合来看,Protobuf在序列化后的空间占用和性能上均优于JSON。因此,在RPC中将序列化和反序列化的格式由JSON改为Protobuf,将为系统性能带来显著的提升。