乐趣区

关于grpc:写给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/programm…

定义音讯类型

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

  • 类型:类型不仅能够是标量类型(intstring等),也能够是复合类型(enum等),也能够是其余message
  • 字段名:字段名比拟举荐的是应用下划线 / 分隔名称
  • 字段编号:一个 messgae 内每一个字段编号都必须惟一的,在编码后其实传递的是这个编号而不是字段名
  • 字段规定:音讯字段能够是以下字段之一

    • singular:格局正确的音讯能够有零个或一个字段(但不能超过一个)。应用 proto3 语法时,如果未为给定字段指定其余字段规定,则这是默认字段规定
    • optional:与 singular 雷同,不过您能够查看该值是否明确设置
    • repeated:在格局正确的音讯中,此字段类型能够反复零次或屡次。零碎会保留反复值的程序
    • map:这是一个成对的键值对字段
  • 保留字段:为了防止再次应用到已移除的字段能够设定保留字段。如果任何将来用户尝试应用这些字段标识符,编译器就会报错

标量值类

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

.proto Type Go Type Notes
double float64
float float32
int32 int32 应用可变长度的编码。对正数的编码效率低下 – 如果您的字段可能蕴含负值,请改用 sint32。
int64 int64 应用可变长度的编码。对正数的编码效率低下 – 如果字段可能有负值,请改用 sint64。
uint32 uint32 应用可变长度的编码。
uint64 uint64 应用可变长度的编码。
sint32 int32 应用可变长度的编码。有符号整数值。与惯例 int32 相比,这些函数能够更高效地对正数进行编码。
sint64 int64 应用可变长度的编码。有符号整数值。与惯例 int64 相比,这些函数能够更高效地对正数进行编码。
fixed32 uint32 始终为 4 个字节。如果值通常大于 2^28,则比 uint32 更高效。
fixed64 uint64 始终为 8 个字节。如果值通常大于 2^56,则比 uint64 更高效。
sfixed32 int32 始终为 4 个字节。
sfixed64 int64 始终为 8 个字节。
bool bool
string string 字符串必须始终蕴含 UTF-8 编码或 7 位 ASCII 文本,并且长度不得超过 232。
bytes []byte 能够蕴含任意长度的 2^32 字节。

复合类型

数组

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-i…

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

    $ apt install -y protobuf-compiler
    $ protoc --version  # Ensure compiler version is 3+
  • MacOS, using Homebrew:

    $ brew install protobuf
    $ protoc --version  # Ensure compiler version is 3+

📖 应用

$ protoc --help
Usage: 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 自身曾经内置该语言对应的编译插件,咱们无需装置

Language Generated Code Source
C++ (include C++ runtime and protoc) C++ src
Java Java java
Python Python python
Objective-C Objective-C objectivec
C# C# csharp
Ruby Ruby ruby
PHP PHP php

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

  • Dart
  • Go

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.yaml
version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

Lint pb 文件

$ buf lint
ecommerce/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.yaml
version: v1
plugins:
  - 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: v1
deps:
  - remote: buf.build
    owner: googleapis
    repository: googleapis
    commit: 75b4300737fb4efca0831636be94e517

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

参考

  • Buf 官网文档
  • Protocol Buffers Documentation
退出移动版