XML、JSON程序员应该都已经再熟悉不过了,不管你使用什么开发语言,解析xml、json是必须玩过的,而且肯定也曾经让你烧过脑,还好大部分开发语言都有很成熟的包来帮你打理这么复杂的事。
XML和Json已经经历了数十年的历史,也是时候做些改变了,Google在08年就推出了一个全新的结构化数据序列化(交换)机制,发展到今天,伴随着微服务架构的到来,已经开始在逐步普及,它就是今天我们要聊的话题是Protocol buffers或简称protobuf。
Protocol buffers是一个灵活,高效,自动化的结构化数据序列化机制,类似于XML,但是更小,更快,更简单。你可以一次定义你希望你的数据结构,使用工具很方便的生成从各种数据流读写结构化数据的代码,支持多种开发语言。你可以在不重新编译部署程序的情况下更新你的数据结构,支持向前和向后兼容。
多的不说了,直接上码吧,类似xml和json,我们先来看看如何来定义数据结构吧:
syntax = "proto3";
option go_package = "user";
option java_package = "com.venusource.protobuf";
message ProtobufUser {
int32 id = 1;
string name = 2;
message Phone{
enum PhoneType {
HOME = 0;
WORK = 1;
OTHER = 2;
}
PhoneType phoneType = 1;
string phoneNumber = 2;
}
repeated Phone phones = 3;
}
protobuf定义详细的语法请到官网自学:https://developers.google.com/protocol-buffers/docs/proto3(你懂的,需要翻墙)。
这里简单介绍一下,message关键字定义一种消息类型,int32、string相当于数据类型,后面的1、2、3相当于字段的索引,在序列化的时候,只使用索引来标识相应的字段,能节省存储空间,emum定义了枚举类型,repeated关键字可以理解为一个数组。
整个结构相当于定义了一个ProtobufUser类,包括id、name、phones三个子段,phones是一个Phone类型的数组,Phone包括两个字段,phoneType和phoneNumber,其中phoneType是枚举类型,包括HOME、WORK、OTHER三种电话类型。
上面的描述相当于下面的json:
{
"Id":1,
"Name":"cjzhao",
"Phones":[
{
"PhoneType":0,
"PhoneNumber":"01088888888"
},
{
"PhoneType":1,
"PhoneNumber":"15588888888"
}
]
}
怎么用呢?我们以go语言为例,先安装Protobuf编译器,可以从github下载最新版本,地址:https://github.com/google/protobuf/releases
安装方法和大部分软件类似,下载安装包解压,配置bin目录到环境变量,这里就不详述了,自己搞吧,如果是mac用户,也可以用brew安装。如果没记错的话安装命令应该是:
brew install protobuf
接下来我们安装go语言相关的工具包和插件,执行如下命令:
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
OK,到此安装已经结束,接下来就是怎么用了。
protobuf使用包括以下几个步骤:
-
定义数据结构(我们在前面已经定义了一个User的结构)。
-
编译成你喜欢的开发语言的相关工具类(可能用词不太准确)。
-
在程序中调用工具类来处理数据。
刚才我们已经定义了数据结构,执行下面的命令可以用成go语言相关的工具类:
mkdir user
protoc --go_out=user ProtobufUser.proto
可以看到在user目录下,生成一个ProtobufUser.pb.go,这个就是编译器生成的类,这里就不贴代码了,太多。
来看看我们怎么使用吧:
func testProtobuf(rw http.ResponseWriter, req *http.Request) {
t := &user.ProtobufUser{Id: 1, Name: "cjzhao",
Phones: []*user.ProtobufUser_Phone{
{PhoneType: user.ProtobufUser_Phone_HOME, PhoneNumber: "01088888888"},
{PhoneType: user.ProtobufUser_Phone_WORK, PhoneNumber: "15588888888"}}}
data, err := proto.Marshal(t)
if err != nil {
log.Fatal("marshaling error: ", err)
}
rw.Write(data)
}
注:本文结尾有本文涉及到的完整代码
很简单,直接定义一个ProtobufUser对象,然后序列化后返回给客户端。
再来看看客户端的代码:
client := &http.Client{}
resp, err := client.Get("http://localhost:8080/protoc")
if err != nil {
log.Println(err)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
}
user := &user.ProtobufUser{}
proto.Unmarshal(data, user)
fmt.Printf("user id=%d, name=%s\n", user.Id, user.Name)
for _, phone := range user.Phones {
fmt.Printf("%s:%s\n", phone.PhoneType, phone.PhoneNumber)
}
也很简单,将服务器端返回的数据直接反序列化即得到我们的user对象。
整个过程protobuf就和xml和json一样,承担了结构化数据的数据交换。
最后再来看看java客户端如何使用吧,执行如下命令,生成java版本的数据结构类:
protoc --java_out=. ProtobufUser.proto
会生成一个ProtobufUser的java类,我们在客户端直接使用即可,客户端代码如下:
package com.venusource.protobuf;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import com.venusource.protobuf.ProtobufUserOuterClass.ProtobufUser;
import com.venusource.protobuf.ProtobufUserOuterClass.ProtobufUser.Phone;
public class ProtobufClient {
public static void main(String args[]){
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost:8080/protoc");
CloseableHttpResponse response = null;
// 执行get请求.
try {
response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
ProtobufUser user = ProtobufUser.parseFrom(EntityUtils.toByteArray(entity));
System.out.printf("user: id=%d, name=%s \n",user.getId(), user.getName());
for(Phone phone:user.getPhonesList()){
System.out.printf("%s:%s\n", phone.getPhoneType(), phone.getPhoneNumber());
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
response.close();
httpclient.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
输出结果如下:
user: id=1, name=cjzhao
HOME:01088888888
WORK:15588888888
怎么样,是不是跨语言、跨平台,和xml和json效果一样。
最后我们来看看和xml和json对比的情况吧,主要包括性能和数据传输包的大小。
同样的数据结构,测试结果如下:
protobuf_test.png可以看出protobuf的性能最好、xml最差,但相差都不是很大。
数据包大小:
protobuf_curl.png可以看出,protobuf最小,42字节,xml最大200字节,相当于5倍。
怎么样?是时候使用protobuf了吧?
最后给大家奉上本文涉及到的代码:
go语言版的性能测试项目:https://github.com/ChangjunZhao/protobuf-test
java版本的客户端代码:https://github.com/ChangjunZhao/protobuf-java-client
本文将是微服务入门系列的第一篇,请在简书关注我哦(cjzhao)。
看完文章有收获的话记得打赏、关注、点赞!
CJ推荐:
IOS APP开发常用的几个命令行工具
使用GitLab来实现IOS项目的持续集成CI
互联网+时代的全新软件(产品)交付模式
程序员的编辑器-VIM(爱就是爱)
向开源社区贡献您的代码
在github上写博客
DevOps是什么东东?
js依赖管理工具bower
JS模块化编程-requirejs
网友评论