gRPC框架
gRPC简介
RPC
RPC 全称远程过程调用(Remote Procedure Call),是一种像本地调用函数的一种平台无关性的接口调用的实现方案。
简单来说就是可以调用另外一个服务器上的方法.
gRPC
gRPC 架构为 CS , protocol buffer 作为客户端和服务端的 Interface Definition Language (IDL) 来描述服务及方法的请求参数和响应结构及消息数据序列化.它支持跨平台和多语言的特性,用 go 写的服务 API,可以通过其他语言调用.
gRPC 核心思想和其他 RPC 架构一样,围绕服务定义,包含有请求参数和响应值的方法,服务端实现服务接口,客户端使用存根进行调用。
Protocol Buffers
定义
Protocol Buffers 是一种平台无关,语言无关的可扩展的用于序列化数据类似于
XML
的接口定义语言(Interface Definition Language)。
语法(proto3)
使用 message 类型进行结构化数据的定义
syntax = "proto3";
message SerachRequest{
string query = 1;
int32 page_number = 2;
int32 result_per_page =3;
}
说明:
第一行指定了 Protocol Buffers
的语法,现在有 proto2
和 proto3
,第一行必须表明语法。每个字段都是一个键值对,并且还要标明每个字段的唯一数值。字段的数值在 1-15 编码占一个字节,16-2047 占2个字节。可以指定字段的规则,单个(默认)或者多个(repeated)。
文件的注释风格
使用的 c/c++ 的 // 或 /* .. */,前者为单行注释,后者为多行注释。
Protocol Buffers 编译成 Go 语言类型对应表
.proto Type | Notes | Go Type |
---|---|---|
double | float64 | |
float | float32 | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 |
uint32 | Uses variable-length encoding. | uint32 |
uint64 | Uses variable-length encoding. | uint64 |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 228. | uint32 |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 256. | uint64 |
sfixed32 | Always four bytes. | int32 |
sfixed64 | Always eight bytes. | int64 |
bool | bool | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text, and cannot be longer than 232. | string |
bytes | May contain any arbitrary sequence of bytes no longer than 232. | []byte |
默认值
如果在编码时没有指定其字段的值则会使用默认值,string 默认值为空字符串, bytes 默认值为空 bytes, bools
默认值为 false,数值类型的默认值为 0。message 类型为对应语言的默认值。
注意: 对于标量 message 字段,一旦被解析就无法显示设置默认值。
保留字段
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
保留字段主要是考虑到兼容性,避免删除字段或注释字段。如一方使用了删除或注释字段而另一方没有使用时,而出现的意外情况。使用保留字段,编译过程会提示错误。
使用其他 message 类型
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url =1;
string titile = 2;
repeated string snippets =3;
}
map 类型
map<string, Project> projects = 3;
RPC service 类型
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
生成代码
生成对应语言的代码参考官网
安装 protoc 编译器[ 手动下载解压至安装目录并加入到环境变量 ]
安装 go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go
注意:官网推荐使用上面的插件,对于 go-zero 项目生成 grpc 代码是老版本 v1.4.x 。
编译 go 文件
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
protoc --proto_path=$SRC_DIR --go_out=$DST_DIR --go_opt=paths=source_relative $SRC_DIR/addressbook.proto
说明:
- I
和 --proto_path
都表示 .proto
文件的所在位置,如果不指定默认为当前位置。
--go_out
表示生成的 Go 代码的输出位置。
$SRC_DIR/addressbook.proto
表示要编译的 .proto
文件。
--go_opt
表示生成 Go 代码的可选项。详情
使用第二种方式生成代码的参数说明:
一个良好的符合 Go 风格中的 .proto
文件中理应加上 option go_package = "[go module name]/[generate code output path]..."
格式行即完整的生成文件输出位置。
--proto_path
或 -I
与输入的 .proto
文件相关,--go_opt=paths=source_relavtive
是相对于 --go_out
的路径。
按照以上示例会在 $DST_DIR
目录下生成 addressbook.pb.go
文件。
踩坑(protoc 版本为 v3.14.0)
- 当 option 不存在
.proto
文件中时,生成文件的包名是安装文件的名称进行生成的,具体文件名参考生成文件。当存在option go_pakcage=bar
或option go_pakcage=github.com/far/bar
包名为 bar 。 - option 选项可以指定文件生成对应语言文件的文件位置和包名,现在规范把 options 加上以指定位置和包名。
option go_package = "路径;包名";
这是其他语言的风格,还是按照 go 的风格进行书写如:option go_package=github.com/example/hello
- 执行
protoc --proto_path=protos/service --go_out=pb protos/service/test.service.v1.proto
若在test.service.v1.proto
文件中存在option go_package=example.com/foo/bar
代码,则会在 pb 目录下自动生成example.com/foo/bar/test.service.v1.pb.go
。若--go_out
所在的 pb 目录不存在, 不会自动生成。 - 执行
protoc --proto_path=protos/service --go_out=pb protos/service/test.service.v1.proto
若在.proto
文件中存在option go_package=bar
选项时或不存在该选项时,会相对于--proto_path
所指目录生成文件。该命令会在 pb 目录下生成test.service.v1.pb.go
文件,若--proto_path=protos
,则生成的内容会改为service/test.service.v1.pb.go
。 --go_opt=paths=source_relative
表示相对于--proto_path
或-I
路径生成目录结构或代码。如执行protoc --proto_path=. --go_out=pb --go_opt=paths=source_relative protos/service/test.service.v1.proto
命令时,会在 pb 目录下自动生成protos/service/test.service.v1.pb.go
;若改成protoc --proto_path=protos/service --go_out=pb --go_opt=paths=source_relative protos/service/test.service.v1.proto
命令,则在 pb 目录下自会生成test.service.v1.proto
文件。
gRPC 使用
获取 gRPC 包
go get google.golang.org/grpc
安装生成 gRPC 代码插件
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
生成 gRPC 代码
protoc -I $SRC --go_out=plugins=grpc:$DST [filepath]
注意:上面能够生成 grpc 代码,是因为 protoc-gen-go 插件中内置了生成 gRPC 代码的插件(2021.1.7补充)
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
推荐使用下面的命令生成代码。
两种命令生成方式解释说明
说明
通过 .proto 文件定义服务,gRPC 允许四种服务方法。
简单 RPC,就像普通函数调用,客户端发送存根到服务器,等待服务器响应。
service RouteGuide{
// a simple RPC method
rpc GetFeature (Point) returns (Feature){}
}
服务器端流式 RPC,客户端发送请求到服务器,获取服务器数据流直到没有数据返回。
service RouteGuide{
// a server-side streaming RPC method
rpc ListFeature (Rectangle) returns (stream Feature){}
}
客户端流式 RPC,客户端使用提供的流,写消息序列到服务器。一旦完成消息队列的写入,就等到服务器将它们全部读取然后响应。
service RouteGuide{
// a client-side streaming RPC method
rpc RecordRoute(stream Point) returns (RouteSummary) {}
}
双向流式 RPC,客户端和服务器都是独立的利用读写流,写一串消息到对方。所以它们可以按照自己的顺序进行读写。
service RouteGuide{
// a bidirectional streaming RPC
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
gRPC 服务的生命周期为:
- 客户端调用 RPC 方法,服务器接收到带有客户端元信息,方法名和超时时间(存在的话)的 RPC 被执行。
- 服务器立马返回服务器的初始元数据(在任何响应之前发送)或等待接收客户端的请求消息,谁先发生特定与应用程序。
- 服务器接收到客户端请求消息后,填充响应消息结构和对应的状态信息(状态码和可选消息)返回。
- 完成调用
对于客户端流、服务器流或双向流声明周期大体相同,区别在于:
- 服务器流:服务端等待接收到客户端消息后,发送完所有响应数据,完成调用;客户端等待服务端响应后完成调用。
- 客户端流:服务端通常(不是必须)等待接收到所有消息后,响应消息,完成调用;客户端发送完所有的消息后完成调用。
- 双向流:它们是相互独立的对象,什么时候完成取决于自身。
代码示例
说明
- gRPC 是CS结构,对于同步和异步问题在大部分语言中有两种方式实现,具体详情查看相关语言的具体实现。
- gRPC 在 Go 中的实现是阻塞同步模式。
- gRPC 使用 Protocol Buffers 作为接口定义语言。
- gRPC 服务调用可以指定超时和元数据。元数据为一组键值对,键为字符串,值为字符串数组。在 Go 中指定超时传递是用过 context 来指定的,其中它的传递过程是可以跨进程的,这是因为它利用了它在 HTTP2 协议头中加入了
grpc-timeout
,通过在服务器端解析协议中的超时标记达到效果。
身份认证
错误处理
关于
版本日志
- 第一版:2020 年 05 月 16 日
- 第二版:2021 年 01 月 08 日
- 第三版:2021 年 02 月 23 日(增加 protoc 工具对生成 grpc 代码的各个参数说明)
credentials 证书
authentication 身份认证、认证方式
authorization 授权