返回 登录
0

Protocol Buffers(protobuf)入门简介及性能分析

声明:本文为CSDN投稿文章,如需转载请联系作者。
作者:赵昌峻,码农、全栈工程师,长期关注云计算、大数据、移动互联网相关技术,从事IT行业十载,有5年企业级应用开发及3年云平台(openstack)研发经历,目前从事移动APP的研发,坚持每天快乐的编程。
责编:陈秋歌,关注前端开发领域,寻求报道或者投稿请发邮件chenqg#csdn.net。
欢迎加入“CSDN前端开发者”微信群,参与热点、难点技术交流。请加群主微信「Rachel_qg」,申请入群,务必注明「公司+职位」。

XML、JSON程序员应该都已经再熟悉不过了,不管你使用什么开发语言,解析xml、JSON是必须玩过的,而且肯定也曾经让你烧过脑,还好大部分开发语言都有很成熟的包来帮你打理这么复杂的事。

XML和JSON已经经历了数十年的历史,也是时候做些改变了,Google在2008年就推出了一个全新的结构化数据序列化(交换)机制,发展到今天,伴随着微服务架构的到来,已经开始在逐步普及,它就是今天我们要聊的话题是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使用包括以下几个步骤:

  1. 定义数据结构(我们在前面已经定义了一个User的结构)。
  2. 编译成你喜欢的开发语言的相关工具类(可能用词不太准确)。
  3. 在程序中调用工具类来处理数据。

刚才我们已经定义了数据结构,执行下面的命令可以用成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的性能最好、xml最差,但相差都不是很大。

数据包大小:

图片描述

可以看出,protobuf最小,42字节,xml最大200字节,相当于5倍。

怎么样?是时候使用protobuf了吧?

最后给大家奉上本文涉及到的代码:

go语言版的性能测试项目:https://github.com/ChangjunZhao/protobuf-test

java版本的客户端代码:https://github.com/ChangjunZhao/protobuf-java-client

评论