写点什么

写给 go 开发者的 gRPC 教程 -protobuf 基础

  • 2023-02-03
    北京
  • 本文字数:5280 字

    阅读完需:约 17 分钟

写给go开发者的gRPC教程-protobuf基础

gRPC是谷歌开源的一款高性能、支持多种开发语言的服务框架,对于一个 rpc 我们关注如下几方面:

序列化协议gRPC使用protobuf,首先使用 protobuf 定义服务,然后使用这个文件来生成客户端和服务端的代码。因为 pb 是跨语言的,因此即使服务端和客户端语言并不一致也是可以互相序列化和反序列化的

网络传输层。gRPC 使用http2.0协议,http2.0相比于HTTP 1.x ,大幅度的提升了 web 性能。

Protobuf IDL

所谓序列化通俗来说就是把内存的一段数据转化成二进制并存储或者通过网络传输,而读取磁盘或另一端收到后可以在内存中重建这段数据

1、protobuf协议是跨语言跨平台的序列化协议。

2、protobuf本身并不是和gRPC绑定的。它也可以被用于非 RPC 场景,如存储等


json xml都是一种序列化的方式,只是他们不需要提前预定义 idl,且具备可读性,当然他们传输的体积也因此较大,可以说是各有优劣


所以先来介绍下protobuf的 idl 怎么写。protobuf最新版本为proto3,在这里你可以看到详细的文档说明:https://protobuf.dev/programming-guides/proto3/

定义消息类型

protobuf里最基本的类型就是message,每一个messgae都会有一个或者多个字段(field),其中字段包含如下元素

  • 类型:类型不仅可以是标量类型(intstring等),也可以是复合类型(enum等),也可以是其他message

  • 字段名:字段名比较推荐的是使用下划线/分隔名称

  • 字段编号:一个 messgae 内每一个字段编号都必须唯一的,在编码后其实传递的是这个编号而不是字段名

  • 字段规则:消息字段可以是以下字段之一

    singular:格式正确的消息可以有零个或一个字段(但不能超过一个)。使用 proto3 语法时,如果未为给定字段指定其他字段规则,则这是默认字段规则

    optional:与 singular 相同,不过您可以检查该值是否明确设置

    repeated:在格式正确的消息中,此字段类型可以重复零次或多次。系统会保留重复值的顺序

    map:这是一个成对的键值对字段

  • 保留字段:为了避免再次使用到已移除的字段可以设定保留字段。如果任何未来用户尝试使用这些字段标识符,编译器就会报错

标量值类

标量类型会涉及到不同语言和编码方式,后续有机会深入讲

复合类型

数组

message SearchResponse {  repeated Result results = 1;}
message Result { string url = 1; string title = 2; repeated string snippets = 3;}
复制代码

枚举

message SearchRequest {  string query = 1;  int32 page_number = 2;  int32 result_per_page = 3;  enum Corpus {    UNIVERSAL = 0;    WEB = 1;    IMAGES = 2;    LOCAL = 3;    NEWS = 4;    PRODUCTS = 5;    VIDEO = 6;  }  Corpus corpus = 4;}
复制代码

服务

定义的 method 仅能有一个入参和出参数。如果需要传递多个参数需要定义成 message

service SearchService {  rpc Search(SearchRequest) returns (SearchResponse);}
复制代码

使用其他消息类型

使用 import 引用另外一个文件的 pb

syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;
message Order { string id = 1; repeated string items = 2; string description = 3; float price = 4; google.protobuf.StringValue destination = 5;}
复制代码

protoc 使用

protoc 就是 protobuf 的编译器,它把 proto 文件编译成不同的语言

📖 安装

https://grpc.io/docs/protoc-installation/

  • Linux, using apt or apt-get, for example:

$ apt install -y protobuf-compiler$ protoc --version  # Ensure compiler version is 3+
复制代码
$ brew install protobuf$ protoc --version  # Ensure compiler version is 3+
复制代码

📖 使用

$ protoc --helpUsage: protoc [OPTION] PROTO_FILES
  -IPATH, --proto_path=PATH   指定搜索路径  --plugin=EXECUTABLE:    ....   --cpp_out=OUT_DIR           Generate C++ header and source.  --csharp_out=OUT_DIR        Generate C# source file.  --java_out=OUT_DIR          Generate Java source file.  --js_out=OUT_DIR            Generate JavaScript source.  --objc_out=OUT_DIR          Generate Objective C header and source.  --php_out=OUT_DIR           Generate PHP source file.  --python_out=OUT_DIR        Generate Python source file.  --ruby_out=OUT_DIR          Generate Ruby source file     @<filename>                proto文件的具体位置
复制代码

1.搜索路径参数

第一个比较重要的参数就是搜索路径参数,即上述展示的-IPATH, --proto_path=PATH。它表示的是我们要在哪个路径下搜索.proto文件,这个参数既可以用-I指定,也可以使用--proto_path=指定。

如果不指定该参数,则默认在当前路径下进行搜索;另外,该参数也可以指定多次,这也意味着我们可以指定多个路径进行搜索。

2.语言插件参数

语言参数即上述的--cpp_out=--python_out=等,protoc 支持的语言长达 13 种,且都是比较常见的

运行 help 出现的语言参数,说明 protoc 本身已经内置该语言对应的编译插件,我们无需安装

下面的语言是由 google 维护,通过 protoc 的插件机制来实现,所以仓库单独维护

3.proto 文件位置参数

proto 文件位置参数即上述的@<filename>参数,指定了我们 proto 文件的具体位置,如proto1/greeter/greeter.proto

📖 语言插件

✨ golang 插件

非内置的语言支持就得自己单独安装语言插件,比如--go_out=对应的是protoc-gen-go,安装命令如下:

# 最新版$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# 指定版本$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.3.0
复制代码

可以使用下面的命令来生成代码

$ protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto
复制代码

注意

protoc-gen-go要求 pb 文件必须指定 go 包的路径,即

option go_package = "liangwt/note/grpc/example/ecommerce";
复制代码
--go_out

指定 go 代码生成的基本路径

--go_opt:设定插件参数

protoc-gen-go提供了 --go_opt 来为其指定参数,并可以设置多个

1、如果使用 paths=import , 生成的文件会按go_package路径来生成,当然是在--go_out目录下,即

$go_out/$go_package/pb_filename.pb.go
复制代码

2、如果使用 paths=source_relative , 就在当前 pb 文件同路径下生成代码。注意 pb 的目录也被包含进去了。即

$go_out/$pb_filedir/$pb_filename.pb.go
复制代码

✨ grpc go 插件

google.golang.org/protobuf中,protoc-gen-go纯粹用来生成 pb 序列化相关的文件,不再承载 gRPC 代码生成功能。

生成 gRPC 相关代码需要安装 grpc-go 相关的插件protoc-gen-go-grpc

 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
复制代码

执行 code gen 命令

$ protoc --go_out=. --go_opt=paths=source_relative \    --go-grpc_out=. --go-grpc_opt=paths=source_relative \    routeguide/route_guide.proto
复制代码
--go-grpc_out

指定 grpc go 代码生成的基本路径

命令会产生如下文件

  • route_guide.pb.go, protoc-gen-go的产出物,包含所有类型的序列化和反序列化代码

  • route_guide_grpc.pb.go, protoc-gen-go-grpc的产出物,包含

    定义在 RouteGuide service 中的用来给 client 调用的接口定义

    定义在 RouteGuide service 中的用来给服务端实现的接口定义

--go-grpc_opt:设定插件参数

protoc-gen-go类似,protoc-gen-go-grpc提供 --go-grpc_opt 来指定参数,并可以设置多个


github.com/golang/protobuf vs google.golang.org/protobuf

github.com/golang/protobuf虽然已经废弃,但网上搜索时经常还能搜到,方便理解整理两者区别。

代码差异

这两个库,google.golang.org/protobufgithub.com/golang/protobuf的升级版本,v1.4.0之后github.com/golang/protobuf仅是google.golang.org/protobuf的包装

功能差异

google.golang.org/protobuf,纯粹用来生成 pb 序列化相关的文件,不再承载 gRPC 代码生成功能。生成 gRPC 相关代码需要安装 grpc-go 相关的插件protoc-gen-go-grpc

github.com/golang/protobuf ,可以同时生成 pb 和 gRPC 相关代码的

用法差异

google.golang.org/protobuf

$ protoc --go_out=. --go_opt=paths=source_relative \    --go-grpc_out=. --go-grpc_opt=paths=source_relative \    routeguide/route_guide.proto
复制代码

github.com/golang/protobuf

$ protoc --go_out=plugins=grpc,paths=import:. \    routeguide/route_guide.proto
复制代码

--go_out的写法是,参数之间用逗号隔开,最后加上冒号来指定代码的生成位置,比如--go_out=plugins=grpc,paths=import:.

--go_out主要的两个参数为pluginspaths,分别表示生成 Go 代码所使用的插件,以及生成的 Go 代码的位置。

plugins参数有不带 grpc 和带 grpc 两种(应该还有其它的,目前知道的有这两种),两者的区别如下,带 grpc 的会多一些跟 gRPC 相关的代码,实现 gRPC 通信


paths参数有两个选项,分别是 importsource_relative,默认为 import

  • import表示按照生成的 Go 代码的包的全路径去创建目录层级

  • source_relative 表示按照 proto 源文件的目录层级去创建 Go 代码的目录层级,如果目录已存在则不用创建。


总之,用google.golang.org/protobuf就对了!

Buf 工具

可以看到使用 protoc 的时候,当使用的插件逐渐变多,插件参数逐渐变多时,命令行执行并不是很方便和直观。例如后面使用到了 grpc-gateway+swagger 插件时

$ protoc -I ./pb \  --go_out ./ecommerce --go_opt paths=source_relative \  --go-grpc_out ./ecommerce --go-grpc_opt paths=source_relative \  --grpc-gateway_out ./ecommerce --grpc-gateway_opt paths=source_relative \  --openapiv2_out ./doc --openapiv2_opt logtostderr=true \  ./pb/ecommerce/v1/product.proto
复制代码

其次依赖某些外部的 protobuf 文件时,只能通过拷贝到本地的方式,也不够方便

因此诞生了✨ Buf 这个项目,它除了能解决上述问题,还有额外的功能

  • 不兼容破坏检查

  • linter

  • 集中式的版本管理

初始化模块

在 pb 文件的根目录执行,为这个 pb 目录创建一个 buf 的模块。此后便可以使用 buf 的各种命令来管理这个 buf 模块了

$ buf mod init
复制代码

此时会在根目录多出一个buf.yaml文件,内容为

# buf.yamlversion: v1breaking:  use:    - FILElint:  use:    - DEFAULT
复制代码

Lint pb 文件

$ buf lintecommerce/v1/product.proto:10:9:Service name "ServiceOrderManagement" should be suffixed with "Service".ecommerce/v1/product.proto:11:18:RPC request type "getOrderReq" should be named "GetOrderRequest" or "ServiceOrderManagementGetOrderRequest".
复制代码

调整 lint 规则

 # buf.yaml version: v1 breaking:   use:     - FILE lint:   use:     - DEFAULT+  except:+    - PACKAGE_VERSION_SUFFIX+    - FIELD_LOWER_SNAKE_CASE+    - SERVICE_SUFFIX
复制代码

生成代码

插件:和使用protoc一样,该装的插件一样要装

插件模版

创建一个buf.gen.yaml ,它是 buf 生成代码的配置。上面的protoc同等功能的buf.gen.yaml可以写成如下形式,相对 protoc 更加直观

# buf.gen.yamlversion: v1plugins:  - plugin: go    out: ecommerce    opt:      - paths=source_relative  - plugin: go-grpc    out: ecommerce    opt:      - paths=source_relative  - name: grpc-gateway    out: ecommerce    opt:      - paths=source_relative      - generate_unbound_methods=true  - name: openapiv2    out: doc    opt:      - logtostderr=true
复制代码

生成代码

buf generate pb
复制代码

buf generate 命令将会

  • 搜索每一个buf.yaml配置里的所有protobuf文件

  • 复制所有protobuf文件到内存

  • 编译所有protobuf文件

  • 执行模版文件里的每一个插件

添加依赖

在使用 grpc-gateway 时依赖了google.api.http,在不使用buf的场景,我们需要手动复制.proto到本地。

buf为我们提供了 Buf Schema Registry (BSR),除了可以使用其他人发布的模块,也可以把我们自己的模块发布到BSR

在模块的文件里声明依赖项

 # buf.yaml version: v1 breaking:   use:     - FILE lint:   use:     - DEFAULT+deps:+  - buf.build/googleapis/googleapis
复制代码

然后执行

buf mod update
复制代码

buf mod update 把你所有的 deps 更新到最新版。并且会生成 buf.lock 来固定版本

# Generated by buf. DO NOT EDIT.version: v1deps:  - remote: buf.build    owner: googleapis    repository: googleapis    commit: 75b4300737fb4efca0831636be94e517
复制代码

此时执行buf generate pb 即使本地没有依赖,也不会再报错缺少依赖了

参考



发布于: 刚刚阅读数: 3
用户头像

微信搜:凉凉的知识库 2018-08-20 加入

大厂资深研发,专注golang、微服务、服务治理领域

评论

发布
暂无评论
写给go开发者的gRPC教程-protobuf基础_golang_凉凉的知识库_InfoQ写作社区