0%

gRPC与Go

gRPC,由 google 开发的一种远程过程调用协议。gRPC 支持多种语言之间的调用,本文主要讲述 gRPC 基于 Go 的实现。

gRPC 简介

gRPC 使用 Protocol Buffer 作为其接口定义语言,使得 gRPC 客户端可以跨机器、跨语言调用 gRPC 服务器的方法。

  • Protocol Buffer 定义接口的输入参数和返回类型

  • gRPC 服务端:实现接口方法,并运行 gRPC Server

  • gRPC 客户端:调用 gRPC Stub (相当于 gRPC Server 在客户端的代理,拥有与 Server 实现的接口方法)的接口

环境安装

Mac 安装 go gRPC 所需环境

protobuf

安装 protobuf

1
brew install protobuf

检查安装是否成功

1
2
❯ protoc --version
libprotoc 28.3

protoc-gen-go、protoc-gen-go-grpc

安装 protoc-gen-go、protoc-gen-go-grpc

1
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
1
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

安装完成后,可以在 ${GOPATH}/bin 下看到 protoc-gen-goprotoc-gen-go-grpc

确认 ${GOPATH}/bin 已添加到环境变量的 PATH 中

Protobuf

Protobuf 是 google 开发的序列化数据格式,定义交换的数据格式,序列化和反序列化数据到各种语言,实现跨语言、跨平台通信。

Protobuf 版本

.proto 文件的开头需要注明使用的 Protobuf 版本号,Protobuf 共有 editions、proto2、proto3 三种版本,建议使用 proto3,下面说明均以 proto3 为例。

1
syntax = "proto3";

如不声明,默认使用 proto2。

数据类型

基础类型

Protobuf 支持多种内置数据类型,并可以映射到各种语言,以 Go 语言为例:

Protobuf Go数据类型 备注
double float64
float float32
int32 int32 负数会占用更多字节
int64 int64
uint32 uint32 字节长度会依据数字大小变化
uint64 uint64
sint32 int32 与 int32 同表示 32 位整数,但 sin32 更适合处理负数场景,正负数相同字节数
sint64 int64
fixed32 uint32 与 uint32 同表示 32 位正整数,fixed32 在处理大于 $2^{28}$ 的数时更高效,永远是 4 字节
fixed64 uint64
sfixed32 int32
sfixed64 int64
bool bool
string string
bytes []byte

定义的 enummessage 也可作为数据类型。

repeated

用于描述数组,可重复各种基础类型,包括内置类型、 enummessage

1
2
3
4
message Example {
repeated int32 numbers = 1; // 一个整数列表
repeated string names = 2; // 一个字符串列表
}

map

用于键值对结构,第一位为 key 类型,第二位 value 类型

1
2
3
4
message Example {
map<string, int32> scores = 1; // 键为字符串,值为整数
map<int32, string> ids = 2; // 键为整数,值为字符串
}

key 和 value 的类型可以是基础类型,包括内置类型、 enummessage ,但不能是 repeated,如果希望 value 为数组,需要先用 message 包装这个 repeated

1
2
3
4
5
6
message Class {
message StudentList {
repeated string name = 1;
}
map<string, StudentList> class_map = 1;
}

enum

enum 枚举类型,类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义一个 enum 类型
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4; // 使用 enum 类型字段
}

enum 的默认值为其 0 值,所以必须在第一位定义 0 值。

enum 可以通过添加 option allow_alias = true; 允许使用别名。

1
2
3
4
5
6
7
enum EnumAllowingAlias {
option allow_alias = true; // 允许别名
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1; // EAA_STARTED 和 EAA_RUNNING互为别名
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}

message

message 消息类型,类型名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式。

1
2
3
4
5
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}

每个字段都要分配唯一的数值标签 tag。已有序号不能随意改动,不然会导致调用失败,在后面添加新 tag 字段不影响旧有调用。

message 类型里支持嵌套其他 messageenum 定义.

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}

在 parent message 外也可以引用到嵌套的 message 类型.

1
2
3
message SomeOtherMessage {
SearchResponse.Result result = 1;
}

service

定义远程调用方法作为客户端与服务端的约定,服务端需实现此接口,用户端调用此接口

1
2
3
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}

生成代码

编辑完 .proto 文件后,便可生成各种语言的代码,供客户端和服务端使用,以 Go 语言为例,生成代码命令如下:

一个简单的 gRPC

实现客户端向服务端查询用户的信息

Protocol Buffer

参考