专栏首页01ZOOProtobuf 扩展指南
原创

Protobuf 扩展指南

本文并非 Protobuf 的基础、语法介绍,更加关注 Protobuf 的扩展用法以及实际案例基础

这部分可以参考官方文档,proto3的语法在这里只做简要的介绍和整理。

一个 基础Message 的定义如下

syntax = "proto3";
import  "other.proto";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  1. 字段一般是以 [ "repeated" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" 格式定义的
  2. 通过 protoc 即其插件,这个 proto 类型的文件会被生成特定语言的结构体,这种语言里面的类型和 proto 文件中的基础类型对应关系在这里,在其他类型的对应关系上,比如 enum、timestamp、duration ( timestamp, duration 为拓展类型)等,由于不同语言的实现方式不同,转换方式也有所不同,取决于 protoc 或插件的实现。
  3. protobuf (如无特别说明,下文中指 protobuf 3)的数据结构通过一个 repeated 关键字实现,同时 v3 也支持了 map 类型。
  4. protobuf 支持嵌套,不支持继承。支持 any,oneof 等特殊的结构,实际取值方式和特定语言有关。
  5. protobuf 的结构体支持转换为 json 而非二进制格式,对应关系在这里,这点值得注意,一般来说一般语言中的结构体转换 json 有自己的转换库函数,但是如果使用 protobuf 的库来转换可能转换结构有所不同,本质原因是因为 protobuf 的库转换时的标准不同。
  6. option的定义格式是 "option" optionName "=" constant ";", 比如 option java_package = "com.example.foo"; options 有内置也有自定义的。这部分和高级部分关系比较大。
    1. options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的内置选项可以在 google/protobuf/descriptor.proto 找到,不同的 option 和他所在的位置对应。
    2. option 有多种类型,比如 fileOption, fieldOption, methodOption 等等
    3. 当你需要自定义一些 option,方式是使用 proto2 的 extend 语法,下面给出了一个例子
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
message MyMessage {
  option (my_option) = "Hello world!";
}

// Java 中获取这个 option 
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

另一个真实的例子,来自 google 的 http 扩展,这里插件会获取 名为 google.api.http 的 option,然后转换为 http 结构

extend google.protobuf.MethodOptions {
  // See `HttpRule`.
  HttpRule http = 72295728;
}


// 实际使用
service Messaging {
    rpc GetMessage(GetMessageRequest) returns (Message) {
        option (google.api.http) = {
                get:"/v1/messages/{message_id}"
            };
    }
}

扩展

protobuf 描述一切

本质上,protobuf 的能力是描述 entitymethod,基本上 entity、method 外加一些约定,就可以描述所有的协议了。事实上 google 的 api 定义 基本上都可以都可以找到 protobuf 的描述

这里 protobuf 的描述作用就可以是

  1. 一个可以被复用的类型 (或者是一种 WellKnownType)。
  2. 一个 rpc 服务的输入输出类型,或者 rpc 服务的 service 以及 method。
  3. 一个描述某种协议(基于 protobuf 扩展 )的元信息结构和扩展位置的约定。
  4. 其他各种内置或扩展的 proto 文件元信息 key value。

甚至,protobuf 能够描述 protobuf 自己。protoc 以及插件的解析 proto 文件原理中最重要的是一个 descriptor 结构,而这个结构也是 protobuf 描述的,这是一个鸡生蛋还是蛋生鸡的关系,事实上,最初的 descriptor 是开发 compile 的开发者手动写的,经过一段时间,再用 protoc 生成 descriptor 文件,用于 protoc 文件 (似乎是一种循环依赖)。

http 扩展

如上所述,google api 中定义了如何将 grpc 映射成 http 的协议,理解这套协议以及实现,是理解扩展 protobuf 的一个很好的出发点。

  1. 首先定义 映射协议以及描述对应关系的 entity, 这个 entity比较简单,文件 大部分实在描述映射的协议,以便于实际实现方参考这个描述来实现。
  2. 使用 protobuf 的 extend option 的方法来扩展协议,这之后就可以使用相关的关键字来定义 google.http.rule, 比如 option (google.api.http) = {get:"/v1/messages/{message_id}}
  3. 实现插件,使用相关的 descriptor 提取 proto 中的信息,转换为 httpRule 结构体,比如 grpc-ecosystem/grpc-gateway 里面的 protoc-gen-grpc-gateway 插件就利用这种方法提取出了 httpRule 结构,然后利用这种结构来实现来 grpc 方法对应的 http handler。比如这个函数 就是提取 httpRule 结构的方法。至此就实现了 结构、协议、proto 文件、生成文件直接的对应转换。
protobuf-http-extension

gogo 扩展

gogo-protobuf 是 protoc 的 go 语言插件的实现,在实现特定语言代码生成的基础了,实现了多种 扩展特性,原始的定义在 这里 , 有以下几类

  1. google.protobuf.EnumOptions/EnumValueOptions:Enum 选项,如 goproto_enum_prefix 表示 enum 前缀开关
  2. google.protobuf.FileOptions:全局/文件选项,如goproto_getters_all 是全局的打开是否生成 get 函数的开关,也有对应的 MessageOptions - goproto_gette
  3. google.protobuf.MessageOptions:类型选项,同上,只是作用范围不同
  4. oogle.protobuf.FieldOptions:字段选项,比较常用的有 nullable - 表示生成指针还是结构体,stdtime 表示转换 WellKnownType timestamp 为 time.Time 等

实际实现和一般的 protoc 的插件并无不同,descriptor 结构由 protoc 解析,插件从 descriptor 进一步的解析出 proto 文件结构,以及各种扩展的选项,然后生成go 语言的文件。

以 nullable 这个选项为例,生成语言文件的时候会使用 帮助函数 判断对应 field 是否设置了 Nullable 的 Extension,如果没设置或者设置为True,则生成的结构则带指针,默认值为 nil。

参考

  1. https://colobu.com/2019/10/03/protobuf-ultimate-tutorial-in-go/
  2. https://colobu.com/2015/01/07/Protobuf-language-guide/
  3. https://blog.csdn.net/xeseo/article/details/12832577

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • golang技术点整理

    defer的执行次序(先进后出)和执行的点(return之前, 返回值赋值之后)。

    王磊-AI基础
  • 扩展 Kubernetes 之 Scheduler

    由于当前的主流扩展方式 Webhook(Scheduler Extender)方式有一些限制:

    王磊-AI基础
  • kubernetes 近期进展 - 1.14-1.19

    王磊-AI基础
  • 解析网站字符串型参数 Javascript QueryString 操作 TQueryString类

    这是学校USRP项目需要而写的一个类,但是既然写出来了,以后也可能用到,就共享出来吧。

    owent
  • volatile关键字

    在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本...

    崔笑颜
  • Python sorted排序方法如何实现

    list_example = [('John', 35), ('Jack', 32), ('Michael', 28), ('Sean', 20)]

    砸漏
  • 新手村:Redis 基础补充知识

    新手村的第一篇文章(新手村:最适合新手的 Redis 基础)是村民的处女作,在之后的学习中回看这篇文章,觉得有一些纰漏之处,甚是惭愧,这也是本篇文章的由来和内容...

    syy
  • 哈希表

    哈希表,又叫散列表,是数据结构的一种。 散列表用途很广泛,比如一个电话薄,每一个姓名对应一个电话号码。姓名与电话号码呈映射关系。假如要创建一个电话薄,可以使用 ...

    多云转晴
  • Buildroot 编译Tips

    默认情况下,buildroot 都会编译第三方依赖库,耗费很久,如何编译五脏俱全的固件呢,它也提供了相关命令

    程序手艺人
  • volatile和synchronized的区别和联系

    volatile 它所修饰的变量不保留拷贝,直接访问主内存中的。    在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄...

    xiangzhihong

扫码关注云+社区

领取腾讯云代金券