protobuf 的一些高级特性

有了一些基本的 protobuf 的概念之后,自然我们就会考虑他的一些高级特性。

message 的嵌套和 group

早期的 protobuf 有个 group 的概念,其实就是把一部分 field 整合在一起,然后可以标注为 optional/repeated 之类的,但是很明显更自然的做法应该是使用一个嵌套的 message 定义,然后在外围的消息里面使用嵌套的消息类型定义一个域。比较 tricky 的是两者访问的形式为有所不同:

  • group 是没有在外围消息里面成员的名字的(有 group 的名字),group 里面的元素标号其实跟外围其他成员的编号在一个范围里面,访问的时候将类型名转换到小写来访问
  • 因为通过 nested message 还是有 field name 的,于是可以跟正常的成员一样访问

message extension 与 polymorphism

常见通过 protobuf 实现的 polymorphism 可以使用一个特性的 field 存放类型,比如使用 enum 表示子类,然后在 message 里面将每个子类独有的部分用 optional 标记,这样可以写个对应的 parser 将 protobuf 传递的消息变换到某个 inheritance hierarchy 上。另一种手法就是借助 extension 这个特性来实现,常见的做法是基类 Base 消息写好之后,子类另外写一个消息 Derived,将独有的 field 放在这个消息里面,在这个 Derived 消息里通过 nested 消息定义 Base 的 extension:一般可以写 extend Base {optional Derived derived = 1000;}。

值得注意的是 extension 对 Base 生成的消息没有任何影响,你可以简单的认为 extension 就是在 Base 的尾部添加一些任意信息,而在 Derived 里面 extend Base 产生的消息本身并不是一个消息,而是为 extension 产生对应的 key 和存放的类型信息。通过 GetExtension/MutableExtension 这些方法获得/设置对应的 extension。这种做法直接传承了“编译时多态”的概念,也就是说拿到 Base 这个消息,在编译时其实你也清楚我需要处理 Derived 这类 Base 消息,因此你会 include 对应的头文件,这样导致你拿到了 Derived::derived 作为 extension 的 key。

一个具体的用例,比如我们有一个 machine learning 的 pipeline,production 上已经有了几个现成的 protobuf 用来描述 pipeline 几个 stage 之间传递的数据格式;现在你希望实验一个新的 feature,理想状态下你得 check-in code 但是不能改动 production 的结构,你需要尽量的 share production 上已有的东西,你的目标就是看看新的 feature 加入后对实验有没有大的影响。extension 是一个很有用的例子,你可以为中间某个 protobuf 提供自己的 extension,加入自己的 feature field,在 pipeline 合适的地方注入这个 extension 的信息,比如在将 protobuf 数据传递给 learner 的时候再把这个 feature 加入到已有的 feature 中,inference 的时候也加入这个 extension 里面的 feature,这时如果之前的 pipeline 设计比较合理的话,对应组件通过继承(或者使用 template)获得增强版的形式就可以 plugin 到已有的 pipeline 里面。

protobuf 的 descriptor 和 option

通过 descriptor 方法(相当于 metaprogramming 里面的 inspection)可以拿到一个 message 的 descriptor,进而你可以通过字符串找到对应 field 的 descriptor,这样你就拿到了这个 field 的 meta information,比如类型信息。option 可以看成是附属于某个 protobuf 类型的 annotation,比如文件、message 或者具体某个 field,常见的默认值可以看成是内置的 option,通过为 google::protobuf::*Option 提供 extension,用户可以添加自己的 option,这个部分可以参看 protobuf 自带的 descriptor.proto 和 descriptor.h。

比较有意思的问题是这个 option 有啥用途没?直观的说,对一般用法没有啥直接意义。protobuf v2 引入了 reflection。可以使用 GetReflection 获得 Reflection 对象,结合对应的 FieldDescriptor 就可以判定任意消息是否拥有某个 field,或者列出某个 message 所有的 field 等。

这部分值得后面进一步研究。

前面还讨论了一些关于 protobuf 与 RPC 之间的关系,其实 protobuf 本身也包括了一些相关的语法用来生成对应的 stub,我们后面再讨论一些与此相关的东西。很显然 google 自己已经有了成熟的实现,但是仅仅开源了 protobuf 这一部分技术。

——————
And Jacob was left alone; and there wrestled a man with him until the breaking of the day.

protobuf 的一些高级特性