一、用go编写简单的gRPC服务
go官方的rpc调用example
[quick start]: https://grpc.io/docs/languages/go/quickstart
gRPC,顾名思义, Google远程过程调用。这是Google创建的一种远程通信协议,可让不同的服务轻松高效地相互通信。它提供与服务之间的同步和异步通信。要了解有关gRPC的更多信息,请访问 gRPC.io
gPRC使用protobuf,一种类型安全的二进制传输格式,旨在实现有效的网络通信。
- Go 1.13+ 1.13 +
安装gRPC相关的库
grpcio-tools主要用根据我们的protocol buffer定义来生成Python代码,官方解释是Protobuf code generator for gRPC。
#apt install python3-pip
pip install grpcio
pip install protobuf
pip install grpcio_tools
安装gRPC编译器
- 安装 protocol 编译器 go插件
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
- 更新您的PATH环境变量,以便protocol 编译器可以找到插件:
$ export PATH="$PATH:$(go env GOPATH)/bin"
protoc-gen-go 是 Go 版本的 proto 文件编译器插件,安装成功后,可在目录 $GOPATH/bin 下看到 protoc-gen-go.exe。 需将 $GOPATH/bin 加入到系统环境变量 PATH 中。
编写proto文件
proto是一个协议文件,客户端和服务器的通信接口正是通过proto文件协定的,可以根据不同语言生成对应语言的代码文件。
heartbeat.proto文件:
syntax = "proto3";
package heartbeat;
message HeartbeatRequest{
string Host = 1;
int32 Mem = 2;
int32 Disk = 3;
int32 Cpu = 4;
int64 Timestamp = 5;
int64 Seq = 6;
}
message HeartbeatResponse{
int32 ErrCode = 1;
string ErrMsg = 2;
}
syntax = "proto3";
package heartbeat;
import "heartbeat.proto";
// HeartBeatService
service HeartBeatService{
rpc HeartBeat(HeartbeatRequest) returns(HeartbeatResponse){}
}
核心 就是一个 用于生成需要用到数据类型的文件;一个就是用于生成相关调用方法的类。 一个定义数据类型,一个用于定义方法。
注意:xxx.proto 里面使用相同报名,这样我们生成的 方法go文件就和数据结构 go文件是一个包下的。 比如这里 xxx_pb2.go、xxx_pb2_grpc.go代码里面 都是 package heartbeat
proto 的 package
proto 的 package关键字很重要,统一套 xxx.proto,在跨语言的情况下,一定不能改package关键字的内容,大家保持一致。
因此, package关键字的内容,其实是作为函数方法的一部分。
例如, 我们定义
package heartbeat_pb;
如上,我们可以把它理解函数方法的一部分。如果你去掉或不一样,你调服务器端程序,就会报错:
我们各种语言本身的包名,和这个参数没有关系。不要因为你的语言你想换包路径改这个参数,你换包路径,不用管这个参数,你可以理解它是grpc自己内部区分的东西。
proto 的 package 不管跨不跨语言,必须必须保持一致。它和语言自身的包路径没有关系!
通过proto生成.go文件
proto文件需要通过protoc生成对应的.go文件。protoc的下载地址 。下载解压之后,将解压目录添加到path的环境变量中。
protoc --proto_path=. --go_out=plugins=grpc:../golang_grpc heartbeat.proto
protoc --proto_path=. --go_out=plugins=grpc:../golang_grpc heartbeat_service.proto
生成的文件名中 xxx_pb2.go 就是我们刚才创建数据结构文件,里面有定义函数参数和返回数据结构; xxx_pb2_grpc.go 就是我们定义的函数,定义了我们客服端rpc将来要调用方法。
编译客户端和服务端代码
遇到问题
did not connect: grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)
client.go 下 dial 时,要指定 conn, err := grpc.Dial(address, grpc.WithInsecure()),否则会报异常:
did not connect: grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)
如果没有使用ssl的话,请使用 grpc.WithInsecure()参数。
rpc error: code = Unimplemented desc = RPC method not implemented
https://stackoverflow.com/questions/56035027/rpc-error-code-unimplemented-desc-rpc-method-not-implemented
rpc error: code = DeadlineExceeded desc = context deadline exceeded
参考URL: https://www.cnblogs.com/029zz010buct/p/9487568.html
最近在将应用的rpc更换为grpc,使用过程中,发现报“rpc error:code=DeadlineExceeded desc = context deadline exceeded”,这是啥?原来是某位仁兄设置了环境的超时时间,但是设置了1S,看好了,是1S。所以,任何稍微费时的交互,都直接报错了。
如果你不显式设置的话,GRPC自己默认的超时时间是一个很大的值,那就不会出现这种问题。
一般而言,server在拿到request之后就应该检测client的超时时间,如果超时了,就不在执行逻辑。
不过,如果在server开始执行逻辑但并没有结束的时候client超时了怎么办
,当然可以在server执行逻辑的同时检测是否超时,如果超时,cancel掉逻辑。但是也有特殊情况,比如这个逻辑很耗费资源
看到了吧,这样的话,超时时间就不用硬编码了,我们可以动态的设定,直到我们的业务和程序运行都收敛了,或许就可以真正硬编码一个超时时间了。
当client超时报错退出之后,server还是会继续进行计算,直到结束,那如果是这样的话,超时的机制对于server来说是没有作用的,即使client已经不再等待这个结果了,但是server还是会继续计算,浪费server的资源。
服务器短检查超时
作为服务器端,如果客户端设置了超时,服务器端就需要去检测下,否则如果客户端已经超时了,服务器端还傻乎乎的干活?岂不是真傻了。
二、Go gRPC超时设置
Go gRPC进阶-超时设置(六)
参考URL: https://www.cnblogs.com/FireworksEasyCool/p/12702959.html
gRPC 系列——grpc超时传递原理
参考URL: https://blog.csdn.net/m0_38031406/article/details/104044904
gRPC默认的请求的超时时间是很长的,当你没有设置请求超时时间时,所有在运行的请求都占用大量资源且可能运行很长的时间,导致服务资源损耗过高,使得后来的请求响应过慢,甚至会引起整个进程崩溃。
客户端请求设置超时时间
1.把超时时间设置为当前时间+3秒
clientDeadline := time.Now().Add(time.Duration(3 * time.Second))
ctx, cancel := context.WithDeadline(ctx, clientDeadline)
// 添加填秒数也可以
//ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
2.响应错误检测中添加超时检测
client := pb.NewHeartBeatServiceClient(conn)
//传入超时时间为3秒的ctx
res, err := client.HeartBeat(ctx, msg)
if err != nil {
fmt.Println("client HeartBeat error: %v", err)
//获取错误状态
errorStatus, ok := status.FromError(err)
if ok {
//判断是否为调用超时
if errorStatus.Code() == codes.DeadlineExceeded {
log.Printf("HeartBeat timeout!")
}
}
log.Printf("Call HeartBeat err: %v", err)
return err
}
服务端判断请求是否超时
客户端自身请求超时,服务器还没有处理完成,服务器端就会出现CLOSE_WAIT状态。
当请求超时后,服务端应该停止正在进行的操作,避免资源浪费。
// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
data := make(chan *pb.SimpleResponse, 1)
go handle(ctx, req, data)
select {
case res := <-data:
return res, nil
case <-ctx.Done():
return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
}
}
func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {
select {
case <-ctx.Done():
log.Println(ctx.Err())
runtime.Goexit() //超时后退出该Go协程
case <-time.After(4 * time.Second): // 模拟耗时操作
res := pb.SimpleResponse{
Code: 200,
Value: "hello " + req.Data,
}
// //修改数据库前进行超时判断
// if ctx.Err() == context.Canceled{
// ...
// //如果已经超时,则退出
// }
data <- &res
}
}
服务端判断请求是否超时#
当请求超时后,服务端应该停止正在进行的操作,避免资源浪费。
三、参考
gRPC介绍与使用 附:开源demo
参考URL: https://blog.csdn.net/u010214802/article/details/89466112
使用gRPC实现golang服务与flutter客户端交互
参考URL:https://www.jianshu.com/p/4f387538c7c0
[推荐]go-grpc-example
参考URL: https://github.com/Bingjian-Zhu/go-grpc-example