gRPC入门(Golang)
gRPC入门
RPC简介
RPC == Remote Procedure Call == 远程过程调用
允许运行于一台计算机的程序调用另一台计算机的子程序。
调用包含了传输协议和编码(对象序列号)协议等等。
gRPC简介
gRPC是一个高性能、开源、通用的RPC框架,基于HTTP2协议标准设计开发,默认采用 Protocol Buffers 数据序列化协议。
Protocol Buffers 是一种与语言、平台无关,可扩展的序列化结构化数据的方法,常用于通信协议,数据存储等等。相较于 JSON、XML,它更小、更快、更简单,因此也更受开发人员的青眯
gRPC特点
1、HTTP/2
2、Protobuf
3、客户端、服务端基于同一份 IDL
IDL是Interface description language的缩写,指接口描述语言
4、移动网络的良好支持
5、支持多语言
概览
讲解
1、客户端(gRPC Sub)调用 A 方法,发起 RPC 调用
2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)
3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回
4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)
5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果
为什么要用gRPC
使用gRPC, 我们可以一次性的在一个
.proto
文件中定义服务并使用任何支持它的语言去实现客户端和服务端,反过来,它们可以应用在各种场景中,从Google的服务器到你自己的平板电脑—— gRPC帮你解决了不同语言及环境间通信的复杂性。使用
protocol buffers
还能获得其他好处,包括高效的序列号,简单的IDL以及容易进行接口更新。总之一句话,使用gRPC能让我们更容易编写跨语言的分布式代码。
安装gRPC
go get -u google.golang.org/grpc
安装Protocol Buffers v3
安装用于生成gRPC服务代码的协议编译器
下载地址:https://github.com/google/protobuf/releases
- bin 目录下的 protoc 是可执行文件。
- include 目录下的是 google 定义的
.proto
文件,我们import "google/protobuf/timestamp.proto"
就是从此处导入。
我们需要将下载得到的可执行文件
protoc
所在的 bin 目录加到我们电脑的环境变量中。
安装Protoc Plugin
编译器插件
我们是使用Go语言做开发,接下来执行下面的命令安装
protoc
的Go插件:
Golang <1.15请使用go get,而不是go install。go get后到对应目录去直接执行go install,执行后会在GOPATH/bin目录生成exe文件,如图:
网上有些安装的不是下面的两个插件,那种现在都是不推荐的,最新的就是使用两个插件,gRPC官网也是使用的两个。
github的那个插件是过去式的,推荐使用google的最新的。
安装go语言插件:
go install google.golang.org/protobuf/cmd/[email protected]
or
go get google.golang.org/protobuf/cmd/[email protected]
该插件会根据
.proto
文件生成一个后缀为
.pb.go
的文件,包含所有
.proto
文件中定义的类型及其序列化方法。
安装grpc插件:
go install google.golang.org/grpc/cmd/[email protected]
or
go get google.golang.org/grpc/cmd/[email protected]
protoc-gen-go-grpc是Google协议缓冲区编译器生成Go代码的插件。
该插件会生成一个后缀为
_grpc.pb.go
的文件,其中包含:
- 一种接口类型(或存根) ,供客户端调用的服务方法。
- 服务器要实现的接口类型。
上述命令会默认将插件安装到
$GOPATH/bin
,为了
protoc
编译器能找到这些插件,请确保你的
$GOPATH/bin
在环境变量中。
两次安装后在GOPATH/bin目录下生成exe,如下图:
安装检查
gRPC入门示例
开发步骤
gRPC开发步骤:
1、编写.proto文件定义服务
普通rpc、服务器流式rpc、客户端流式rpc、双向流式rpc
2、生成指定语言的代码
使用编译器插件生成客户端和服务端代码。
3、编写业务逻辑代码
在服务端编写业务代码实现具体的服务方法,在客户端按需调用这些方法
项目结构
虽然两个pb文件夹内的东西都是一样的,不要想着把pb文件夹放到外面同时供server和client使用,因为在真实场景中,rpc是远程调用,server和client并不在同一台机器。
编写proto代码
syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本
option go_package = "...."; // 指定编译生成的文件目录,也可以指定golang包名
package pb; // 默认包名
// 定义服务
service Greeter {
// SayHello 方法
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
// 请求消息
message HelloRequest {
string name = 1;
}
// 响应消息
message HelloResponse {
string replay = 1;
}
service
对应 golang 中的接口message
就是需要传输的数据格式的定义,对应 golang 中的结构体
编写Server端Go代码
src下新建一个 go_grpc_example 文件夹,在它下面再新建一个 hello_server 文件夹,新建一个空白的 main.go, 在项目根目录下执行
go mod init hello_server
然后再新建一个 pb 文件夹,把上面写的 proto 代码保存到 hello.proto,放入pb文件夹内。
此时,项目目录结构为:
go_grpc_example/hello_server
├── go.mod
├── go.sum
├── main.go
└── pb
└── hello.proto
然后把 hello.proto 文件中的 go_package 修改,如下:
// 分号前是编译生成的.pb.go文件存放地址,分号后是所属包名,这个包名覆盖默认包名
option go_package = "hello_server/pb;pb";
然后在 pb 目录下执行以下命令,根据 hello.proto 生成go源码文件
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
这里涉及到proto命令,可以看本文后面的Protobuf命令学习章节了解
生成后的go源码文件会保存在 pb 文件夹下,如下:。
go_grpc_example/hello_server
├── go.mod
├── go.sum
├── main.go
└── pb
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
把下面的内容添加到
hello_server/main.go
中。
package main
import("context""fmt""hello_server/pb""net""google.golang.org/grpc")// hello servertype server struct{
pb.UnimplementedGreeterServer
}func(s *server)SayHello(ctx context.Context, in *pb.HelloRequest)(*pb.HelloResponse,error){return&pb.HelloResponse{Replay:"Hello "+ in.Name},nil}funcmain(){// 监听本地的8972端口
lis, err := net.Listen("tcp",":8972")if err !=nil{
fmt.Printf("failed to listen: %v", err)return}
s := grpc.NewServer()// 创建gRPC服务器
pb.RegisterGreeterServer(s,&server{})// 在gRPC服务端注册服务// 启动服务
err = s.Serve(lis)if err !=nil{
fmt.Printf("failed to serve: %v", err)return}}
- 创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象
- 将 server(其包含需要被调用的服务端接口)注册到 gRPC Server 的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
- 创建 Listen,监听 TCP 端口
- gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStop
编译并执行
hello_server
:
go build
hello_server.exe
编写Client端Go代码
步骤和Server端相同。
在 go_grpc_example 文件夹下再新建一个 hello_client 文件夹,新建一个空白的 main.go, 在项目根目录下执行
go mod init hello_client
然后再新建一个 pb 文件夹,把上面写的 proto 代码保存到 hello.proto,放入pb文件夹内。
此时,项目目录结构为:
go_grpc_example/hello_client
├── go.mod
├── go.sum
├── main.go
└── pb
└── hello.proto
然后把 hello.proto 文件中的 go_package 修改,如下:
option go_package = "hello_client/pb;pb";
然后在 pb 目录下执行以下命令,根据 hello.proto 生成go源码文件
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
生成后的go源码文件会保存在 pb 文件夹下,如下:。
go_grpc_example/hello_client
├── go.mod
├── go.sum
├── main.go
└── pb
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
把下面的内容添加到
hello_client/main.go
中。调用
http_server
提供的
SayHello
RPC服务。
package main
import("context""flag""log""time""hello_client/pb""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure")// hello_clientconst(
defaultName ="world")var(
addr = flag.String("addr","127.0.0.1:8972","the address to connect to")
name = flag.String("name", defaultName,"Name to greet"))funcmain(){
flag.Parse()// 连接到server端,此处禁用安全传输
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err !=nil{
log.Fatalf("did not connect: %v", err)}defer conn.Close()
c := pb.NewGreeterClient(conn)// 执行RPC调用并打印收到的响应数据
ctx, cancel := context.WithTimeout(context.Background(), time.Second)defercancel()
r, err := c.SayHello(ctx,&pb.HelloRequest{Name:*name})if err !=nil{
log.Fatalf("could not greet: %v", err)}
log.Printf("Greeting: %s", r.GetReplay())}
- 创建与给定目标(服务端)的连接交互
- 创建 server的客户端对象
- 发送 RPC 请求,等待同步响应,得到回调后返回响应结果
- 输出响应结果
保存后将
http_client
编译并执行:
go build
hello_client.exe -name=ersan
运行结果
得到如下结果,说明RPC调用正确:
Protobuf命令学习
-I (-proto_path)
protoc --proto_path=proto --go_out=. proto/hello.proto
--proto_path` 可以简写为`-I` ,表示读取`*.proto文件的目录
–go_out
protoc --go_out=. hello.proto // 不使用-I,直接进入proto的文件夹执行
--go_out
参数是用来指定 protoc-gen-go 插件的工作方式和Go代码的生成位置
--go_out` 表示将`*.proto文件`转换为`golang语言
= 前半部分是用转换成哪个语言,go_out表示转换为go语言。
= 后半部分,表示的是转换生成后的pb.go文件,放在哪个文件夹下。
. 或者 :. 都表示将生成的pb.go文件放入当前的目录下。
使用–go_out把proto文件转为golang语言时,因为protobuf的源码服务中没有对go语言的支持,所以我们需要proto-gen-go插件( 该插件会根据
.proto
文件生成一个后缀为
.pb.go
的文件,包含所有
.proto
文件中定义的类型及其序列化方法。)来生成go代码。
–go_out的写法常见的比如:
--go_out=paths=import:.
、
--go_out=paths=source_relative:.
,或者
--go_out=plugins=grpc:.
从这里可以看出
--go_out
主要的两个参数是
plugins
和
paths
,分别表示生成Go代码所使用的插件,以及生成的Go代码的位置。
paths
参数有两个选项,分别是
import
和
source_relative
,默认为 import,表示按照生成的Go代码的包的全路径去创建目录层级,source_relative 表示按照 proto源文件的目录层级去创建Go代码的目录层级,如果目录已存在则不用创建。
plugins
:指定依赖的插件
指定源文件
protoc --go_out=:. ./hello.proto
protoc --go_out=:. hello.proto
上面两个等价,都是读取当前目录下的hello.proto文件。我们用
hello.proto
文件作为执行源头文件。
指定gprc选项,生成grpc功能
我们一般如果是go语言的话,用proto都是结合grpc来用的。
protoc --go_out=plugins=grpc:. hello.proto
它表示用grpc服务生成 pb.go 文件。它执行的时候,会检查是否已经下载grpc库(
google.golang.org/grpc
)。
要想使用grpc功能,那么proto文件里得定义rpc相关的服务,这样生成的pb.go文件,才会生成相关rpc数据
注意这里的
:.
就和之前不同了,因为之前没有生成grpc插件,所以
:.
=
.
,现在需要使用grpc插件,那么这里的
:
就是一个分隔符。分割成两部分:
--go_out === plugins的类型 : 输出的路径
单独使用这里的proto命令会报错 “plugins are not supported” ,下面会讲到
–go_opt
–go_opt表示生成go文件时候的目录选项
–go_opt=paths=source_relative 表示 生成的文件与proto在同一目录
完整编译
在
.proto
文件的目录下,使用如下命令编译:
protoc --go_out=. --go-grpc_out=. ./hello.proto
它使用到了我们在上面安装的两个插件,它会生成两个文件,分别是
.pb.go
和
_grpc.pb.go
两个文件,前者主要是对 message 生成对应的结构体和方法,后者生成
gRPC
,主要是对 service 生成对应的 interface 和方法
另外,还有一个
--go_opt=paths=source_relative
命令,其含义代表生成的
.pb.go
文件路径不依赖于
.proto
文件中的
option go_package
配置项,直接在
go_out
指定的目录下生成
.pb.go
文件(
.pb.go
文件的package名还是由
option go_package
决定)
--go-grpc_opt=paths=source_relative
,针对
_grpc.pb.go
文件,作用同上。
之前我们提到一个 “plugins are not supported” 问题,当前版本编译时,之前的方法
protoc --go_out=plugins=grpc:. *.proto
不再使用,转而用
protoc --go_out=. --go-grpc_out=. ./hello.proto
代替。
github的方式,需要使用
--go_out=plugins=grpc
来去进行生成,而在golang.org方式中,弃用了这种方式,使用
protoc-gen-go
将不在支持gRPC service的定义,需要使用新的插件
protoc-gen-go-grpc
。
版权归原作者 尤其是十月的风 所有, 如有侵权,请联系我们删除。