Protocol buffers(protobuf)入门简介及性

作者: cjzhao | 来源:发表于2016-09-28 15:04 被阅读3600次

    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

    相关文章

      网友评论

        本文标题:Protocol buffers(protobuf)入门简介及性

        本文链接:https://www.haomeiwen.com/subject/rmnhyttx.html