用go编写简单的gRPC服务、Go gRPC超时设置

一、用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编译器

  1. 安装 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
  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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值