protobuf 学习笔记

来源:互联网 发布:淘宝上买东西如何退货 编辑:程序博客网 时间:2024/06/06 15:53

Protobuf

名称:protocol buffer    简称:PB    类别:google的一种数据交换的格式    特点:二进制,空间占用少,语言无关,平台无关,直接工具生成代码
缺点: 不具有可读性(二进制)

它是什么


  • 术语定义
    • protocol buffer 官网github定义:https://github.com/google/protobuf  
      • https://developers.google.com/protocol-buffers/  开发者帮助
    • 百度百科定义:http://baike.baidu.com/link?url=vlbJtV9p9Vfj4wQDif_XaNymBZI67u3AFjF5ZpT6iJjOwGfsenwUZMieBGrtlUC0FkWwWQic-Y3rwXYP0tq9Uq
      • protocol buffer(以下简称PB)是google 的【类型】一种数据交换的格式【特点】它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
  • 相关语法
    • 属性数据类型对照如下图:
                 
  • 它的历史
    • Protobuf 的 proto3 与 proto2 的区别

      总的来说,proto3 比 proto2 支持更多语言但 更简洁。去掉了一些复杂的语法和特性,更强调约定而弱化语法。如果是首次使用 Protobuf ,建议使用 proto3 。

      1. 在第一行非空白非注释行,必须写:

        syntax = "proto3";

      2. 字段规则移除了 “required”,并把 “optional” 改名为 “singular”;

        在 proto2 中 required 也是不推荐使用的。proto3 直接从语法层面上移除了 required 规则。其实可以做的更彻底,把所有字段规则描述都撤销,原来的 repeated 改为在类型或字段名后加一对中括号。这样是不是更简洁?

      3. 语言增加 Go、Ruby、JavaNano 支持;

      4. 移除了 default 选项;

        在 proto2 中,可以使用 default 选项为某一字段指定默认值。在 proto3 中,字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。

        在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。

        但这样就无法区分某字段是根本没赋值,还是赋值了默认值。这在 proto3 中问题不大,但在 proto2 中会有问题。

        比如,在更新协议的时候使用 default 选项为某个字段指定了一个与原来不同的默认值,旧代码获取到的该字段的值会与新代码不一样。

        另一个重约定而弱语法的例子是 Go 语言里的公共/私有对象。Go 语言约定,首字母大写的为公共对象,否则为私有对象。所以在 Go 语言中是没有 public、private 这样的语法的。

      5. 枚举类型的第一个字段必须为 0 ;

        这也是一个约定。

      6. 移除了对分组的支持;

        分组的功能完全可以用消息嵌套的方式来实现,并且更清晰。在 proto2 中已经把分组语法标注为『过期』了。这次也算清理垃圾了。

      7. 旧代码在解析新增字段时,会把不认识的字段丢弃,再序列化后新增的字段就没了;

        在 proto2 中,旧代码虽然会忽视不认识的新增字段,但并不会将其丢弃,再序列化的时候那些字段会被原样保留。

        我觉得还是 proto2 的处理方式更好一些。能尽量保持兼容性和扩展能力,或许实现起来也更简单。proto3 现在的处理方式,没有带来明显的好处,但丢掉了部分兼容性和灵活性。

      8. 移除了对扩展的支持,新增了 Any 类型;

        Any 类型是用来替代 proto2 中的扩展的。目前还在开发中。

        proto2 中的扩展特性很像 Swift 语言中的扩展。理解起来有点困难,使用起来更是会带来不少混乱。

        相比之下,proto3 中新增的 Any 类型有点想 C/C++ 中的 void* ,好理解,使用起来逻辑也更清晰。

      9. 增加了 JSON 映射特性;

        语言的活力来自于与时俱进。当前,JSON 的流行有其充分的理由。很多『现代化』的语言都内置了对 JSON 的支持,比如 Go、PHP 等。而 C++ 这种看似保罗万象的学院派语言,因循守旧、故步自封,以致于现出了式微的苗条。

  • 同类技术比较:
    • xml  优点:语义明确,结构清晰       缺点: 占用空间
    • json  优点:语义相对可理解,结构清晰       缺点: 相比protobuf占空间
  • 学习前提/依赖
    • 要有数据类型概念(int,string),要有相关需要使用该数据结构的语言基础,比如java
  • 注意事项:
    • 每个field都是唯一数字的标记,这是用来标记这个field在message二进制格式中的位置的,一旦使用就不能再修改顺序了
      注:标记从1-15只有一个字节编码,包括自增长属性(更多的见Protocol Buffer Encoding)
      标记从16-2047占用两个字节。因此尽量频繁使用1-15,记住为未来的扩展留下一些位置。
      最小的tag你可以定义为1,最大2的29次方-1  536870922.你同样不能使用19000-19999(这个位置已经被GPB自己实现)
    • 当含有optional字段的message从流转换成对象的时候,如果没有包含optional字段的数据,那么对象的optional字段会设置成默认值。
      默认值可以作为message的描述出现。举个例子:
      optional int32 result_per_page = 3 [default = 10];
      果没有指定默认值的话,string 默认为空串,bool 默认为false,数字类型默认0,枚举类型,默认为类型定义中的第一个值,

为什么会出现


  • 使用 Protocol Buffers 代替 JSON 的五个原因    原文: http://www.oschina.net/translate/choose-protocol-buffers
  • 原因 #1: 模式本身很不错

    有一种痛苦的讽刺指向一个事实,我们小心谨慎地在我们的数据库里面编写数据模型,维护各个层次的代码,保持这些数据模型处于控制之中,当我们想要发送数据连接到另一个服务的时候,要求所有的疑虑都要被考虑到。然而,我们往往依靠的是在边界上与我们的系统之间不一致的代码,我们的系统不能强制结构化我们的数据组件,这是如此的重要,编码的语义是你曾经的业务对象,在proto格式中,它足以帮助并保证应用程序之间的信号不会丢失,而界限就在你所创建并执行的业务规则。


    原因 #2: 无偿地向后兼容

    被编号的字段在proto的定义中排除了所需的版本检查,这是其中一个被明确表述的动机(为什么这样设计和实现Protocol Buffers)。如同开发者文档中声明的那样,协议被设计成能在一定程度上避免出现像下面这样的“丑陋的代码”,下面的代码用来检测协议的版本:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    if (version == 3) {
      ...
    else if (version > 4) {
      if (version == 5) {
        ...
      }
      ...
    }

    同编号字段一样, 你必须改变编码习惯,朝着能向老版本维护和向后兼容的方向改变。正如在文档中的声明那样,曾经 Protocol Buffers 是这样被介绍的:

    “新的字段可以很容易被引入,并且不需要中间服务去检查数据就能被解析,通过数据不必知道所有的字段。”

    已经部署各种JSON的服务器已经遭受各种与发展模式以及向后兼容的相关问题。我现在深信编号字段能防止错误,并且能在新功能和服务的推出上做到简化。

    原因 #3: 更少的样本代码

    除了显式的版本检查和缺乏后续的兼容性,JSON终端在HTTP上的基础服务通常依赖专门的手写样板代码去处理Ruby对象的编码和解码。解析和反解析类常常包含隐藏的业务逻辑,它暴露了手动解析每个新的数据类型的缺陷,当一个类通过Protocol Buffers产生(你一般就不会再去触碰它),它能提供大量相似的方法,还避免了大量头痛的事情。随着模式的发展,你将会用proto产生类(应当承认,一旦你更新他们),你可以把更多的空间留给你所关注的挑战(保持你的应用运行和持续构建产品)。

    原因 #4: 验证和可扩展性

    required,optional 和 repeated关键字在Protocol Buffers中的定义是非常强大的。它们允许你去编码,在模式级别,形象化你的数据结构和去实现类怎样工作(每种编程语言处理)的细节。Ruby的protocol_buffers库将会提升异常,例如:如果一个对象实例没有填写必填的字段,你试着去对这样一个对象实例编码,就会提升异常。通过简单地编辑一个新的编号字段的值,你可以把一个字段从required变成optional或者反之亦然。有了这种灵活编码的语义序列化格式,大大增强了其功能。

    因为你还可以嵌入proto,定义内部的其他成员,你也可以拥有通用的Request和Response结构,它还允许其他数据结构的传输并确保传输连接上,它为服务器间通讯实现真正的灵活性和安全的数据传输提供了机会。类似Riak的数据库系统使用Protocol Buffers有巨大的效果——因为有了一些启示,我建议重新审视那些接口。

    原因#5:建议的语言互操作性

    因为Protocol Buffers已经被多种语言实现,在你的架构中多语言混合的应用程序之间的互操作性变得更简单。如果你引入了一个新的服务在JAVA或者GO中,甚至和用Node或者Clojure或者Scala实现的后端通讯,你只需简单的把proto文件交给目标语言编写的代码生成器,你将在这些架构之间获得较好的安全和互操作性。平台特定数据类型的细节被目标语言处理,你将更多的关注你的问题的困难部分,而不是匹配字段和数据类型在JSON的编码和解码方案中。

    什么时候更适合使用JSON?

    有些时候JSON比Protocol Buffers更适合,包括如下的场景:

    • 你需要或者想让数据对人是可读的

    • 来自于服务的数据是直接发送到web浏览器

    • 你的服务端应用程序是用javaScript编写的

    • 你不准备把数据模型绑定到模式上

    • 你没有带宽添加另外一个工具到你的军火库

    • 运行不同类型的网络服务的运营负担过大

    可能还有更多的情况。最后,总之,这是很重要的在心里权衡和不要盲目的选择一项技术

    结论

    Protocol Buffers提供了几种相对JSON在内部服务之间在线传输数据的引人注目的优势。并没有完全的替换JSON,特别是服务和web浏览器直接通讯的情况,Protocol Buffers提供了真正的优势不仅在上面概述的方法,也编解码的速度和数据大小上有更多的优势。

    你可以从你的应用程序中提取出哪些服务?如果你今天不得不做出选择,你会选择JSON还是Protocol Buffers?让我们讨论讨论-我们愿意在下面的评论中听到更多关于你在使用JSON和Protocol Buffers上的经验。

  • 官方解释
    • are simpler   更小巧
    • are 3 to 10 times smaller  速度更快
    • are 20 to 100 times faster
    • are less ambiguous   缺少歧义
    • generate data access classes that are easier to use programmatically  程序自动生成数据实体,方便

哪些人不喜欢它


  • 对于一些要求可读性比较高的数据类型时,不适合使用protobuf,比如直接返回前端界面,或者前端js等数据

为什么学习它


  • 解决工作问题
  • 知识储备

我要怎么做(按优先级从高到低排序)


  • 自己写 Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.shunwang.swbox.common.bo.redis.proto;
 
option java_package = "com.shunwang.swbox.common.bo.redis.proto";
option java_outer_classname = "IssMatchRanking";
 
//赛事榜单
message IssMatchRankingProto {
  optional int32 rowNum = 1;//这的数字是属性的排序
  optional int32 matchUserId = 2;
  optional int32 gameId = 3;
  optional int32 matchId = 4;
  optional int32 userId = 5;
  optional string subAccount = 6;
  optional string roleName = 7;
  optional string serverName = 8;
  optional string barName = 9;
  optional int32 matchNum = 10;
  optional int32 killManCnt = 11;
  optional int32 hurtCnt = 12;
  optional int32 highestWinCnt = 13;
  optional int32 battleCnt = 14;
  optional string grading = 15;
  optional string reward = 16;
}
//赛事榜单列表
message IssMatchRankingListProto {
  repeated IssMatchRankingProto issMatchRanking = 1;
}
    
1. 如上述代码,是需要对需要的数据结构进行构建一个.proto的文件 。以下有官网demo
https://developers.google.com/protocol-buffers/docs/overview#how-do-they-work

2. 下载protobuf工具包:https://github.com/google/protobuf/releases/tag/v2.6.1  自己挑选对应版本
这里注意,需要下载里面的protoc-2.6.1-win32.zip 然后解压到上面下载的工具包目录下

3. 然后在上述工具目录下cmd执行  protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/IssMatchRanking.proto 就会生成对应的java
件。之后就可以愉快的相互转换了,如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  /**
     * 从缓存中 获取比赛排名信息
     * @param key
     * @return
     */
    private List<IssMatchRanking> getRankingFromCache(byte[] key) {
        List<IssMatchRanking> issMatchRanking = null;
        try {
            byte[] values = redisTool.get(key);
            if (values != null) {
                issMatchRanking = new ArrayList<IssMatchRanking>();
                 
                IssMatchRankingListProto issMatchRankingListProto = IssMatchRankingListProto.parseFrom(values);
                if (issMatchRankingListProto.getIssMatchRankingCount() > 0) {
                    List<IssMatchRankingProto> IssMatchRankingProtos = issMatchRankingListProto.getIssMatchRankingList();
                    for (IssMatchRankingProto issMatchRankingProto : IssMatchRankingProtos) {
                        issMatchRanking.add(this.protoToRanking(issMatchRankingProto));
                    }
                }
            }
        catch (InvalidProtocolBufferException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return issMatchRanking;
    }
 
       /**
     * IssMatchUserExtProto 转 IssMatchUserExt
     * @param issMatchProto
     * @return
     */
    public IssMatchRanking protoToRanking(IssMatchRankingProto issMatchRankingProto) {
        IssMatchRanking issMatchRanking = new IssMatchRanking();
        issMatchRanking.setRowNum(issMatchRankingProto.getRowNum());
        issMatchRanking.setMatchUserId(issMatchRankingProto.getMatchUserId());
        issMatchRanking.setGameId(issMatchRankingProto.getGameId());
        issMatchRanking.setMatchId(issMatchRankingProto.getMatchId());
        issMatchRanking.setUserId(issMatchRankingProto.getUserId());
        issMatchRanking.setSubAccount(issMatchRankingProto.getSubAccount());
        issMatchRanking.setRoleName(issMatchRankingProto.getRoleName());
        issMatchRanking.setServerName(issMatchRankingProto.getServerName());
        issMatchRanking.setBarName(issMatchRankingProto.getBarName());
        issMatchRanking.setMatchNum(issMatchRankingProto.getMatchNum());
        issMatchRanking.setKillManCnt(issMatchRankingProto.getKillManCnt());
        issMatchRanking.setHurtCnt(issMatchRankingProto.getHighestWinCnt());
        issMatchRanking.setHighestWinCnt(issMatchRankingProto.getHighestWinCnt());
        issMatchRanking.setBattleCnt(issMatchRankingProto.getBattleCnt());
        issMatchRanking.setReward(issMatchRankingProto.getReward());
        issMatchRanking.setGrading(issMatchRankingProto.getGrading());
        return issMatchRanking;
    }


  • 参考别人 Demo
    • Gtihub 搜索 Demo:https://github.com/search/advanced
    • Git@OSC 搜索 Demo:http://git.oschina.net/
1 0