本篇为【写给go开发者的gRPC教程】系列第一篇

  • 第一篇:protobuf基础
  • 第二篇:通讯模式
  • 第三篇:拦截器

gRPC是谷歌开源的一款高功用、支撑多种开发言语的服务结构,关于一个rpc咱们重视如下几方面:

序列化协议gRPC运用protobuf,首要运用protobuf界说服务,然后运用这个文件来生成客户端和服务端的代码。由于pb是跨言语的,因而即便服务端和客户端言语并不一致也是能够相互序列化和反序列化的

网络传输层。gRPC运用http2.0协议,http2.0比较于HTTP 1.x ,大幅度的提升了 web 功用。

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

Protobuf IDL

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

1、protobuf协议是跨言语跨渠道的序列化协议。

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

json xml都是一种序列化的方法,仅仅他们不需求提前预界说idl,且具备可读性,当然他们传输的体积也因而较大,能够说是各有优劣

所以先来介绍下protobuf的idl怎么写。protobuf最新版本为proto3,在这里你能够看到具体的文档阐明:protobuf.dev/programming…

界说音讯类型

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

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

  • 类型:类型不仅能够是标量类型(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文件编译成不同的言语

装置

grpc.io/docs/protoc…

  • 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/protobuf github.com/golang/protobuf
代码差异 新;推荐运用 旧;
v1.4.0后仅是google.golang.org/protobuf的包装
功用差异 功用朴实:
只生成pb序列化相关的文件
生成gRPC相关代码需求运用grpc-go插件protoc-gen-go-grpc
一起生成pb和gRPC相关代码
用法差异 protoc --go_out=. --go_opt=paths=source_relative routeguide/route_guide.proto protoc --go_out=plugins=grpc,paths=import:. routeguide/route_guide.proto
代码差异

这两个库,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

✨ 微信公众号【凉凉的知识库】同步更新,欢迎重视获取最新最有用的后端知识 ✨