为什么要使用Deadlines

当我们使用gRPC时,gRPC库关系的是连接,序列化,反序列化和超时执行。Deadlines 允许gRPC客户端设置自己等待多长时间来完成rpc操作,直到出现这个错误 DEADLINE_EXCEEDED。但是在正常情况下,这个DEADLINE_EXCEEDED默认设置是一个很大的数值。

一些语言的API用deadline,一些用 timeout。

在正常情况下,你没有设置deadline,那么所有的请求可能在最大请求时间过后才超时。这样你对于你的服务器资源,可能存在风险,比如内存,可能因为这个长期运行的服务而增长很快,从而耗尽资源。

为了避免这种情况,需要给你的客户端请求程序设置一个默认的超时时间,在这段时间内请求没有返回,那么就超时报错。

怎么使用?Deadlines使用步骤

使用步骤:

1.设置deadlines

var deadlineMs = flag.Int("deadline_ms", 20*1000, "Default deadline in milliseconds.")

clientDeadline := time.Now().Add(time.Duration(*deadlineMs) * time.Millisecond)
ctx, cancel := context.WithDeadline(ctx, clientDeadline)

2.检查deadlines

if ctx.Err() == context.Canceled {
return status.New(codes.Canceled, "Client cancelled, abandoning.")
}

具体使用:

1. 建立连接时超时控制:

客户端建立连接时,使用的Dial()函数,它位于

google.golang.org/grpc/clientconn.go 中,我们看看这个函数内容:

func Dial(target string, opts ...DialOption) (*ClientConn, error) {    
return DialContext(context.Background(), target, opts...)
}

它里面调用的 DialContext() 函数,这个函数非常长,他们在同一个文件中,它是实际执行的函数,这里面就有context的timeout和Done相关操作。你也可以到google.golang.org/grpc/clientconn.go文件中去看看这个函数DialContext具体是干嘛的。

使用的时候传入设置timeout的context,如下:

ctx, cancel := context.Timeout(context.Bakcground(), time.Second*5)
defer cancel()
conn, err := grpc.DialContext(ctx, address, grpc.WithBlock(), grpc.WithInsecure())
  • grpc.WithInsecure() ,这个参数啥意思?

    gRPC是建立在HTTP/2上的,所以对TLS提供了很好的支持。如果在客户端建立连接过程中设置 grpc.WithInsecure() 就可以跳过对服务器证书的验证。写练习时可以用这个参数,但是在真实的环境中,不要这样做,因为有泄露信息的风险。
  • grpc.WithBlock()

    这个参数会阻塞等待握手成功。

    因为用Dial连接时是异步连接,连接状态为正在连接,如果设置了这个参数就是同步连接,会阻塞等待握手成功。

    这个还和超时设置有关,如果你没有设置这个参数,那么context超时控制将会失效。

2. 调用时超时:

函数的调用超时控制

ctx, cancel := context.WithTimeout(context.TODO(), time.Second*5)
defer cancel()
result, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

实例

用grpc官方的例子来练习下

目录结构:

grpc-tutorial
-- 04deadlines
--client
- main.go
--server
- main.go
--proto/echo
- echo.proto
- echo.pb.go

1.首先是定义服务 echo.proto

syntax = "proto3";
package echo;message EchoRequest {   
string message = 1;
} message EchoResponse {   
string message = 1;
} service Echo {   
rpc UnaryEcho(EchoRequest) returns (EchoRequest) {}   
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {}   
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {}   
rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse){}
}

进入到proto/echo目录,生成go文件,命令如下:

protoc -I . --go_out=plugins=grpc:. ./echo.proto

2.客户端

client\main.go

有2个主要的函数,2端都是stream和都不是stream,先看都不是stream的函数

都不是stream的函数

// unaryCall 不是stream的请求
func unaryCall(c pb.EchoClient, requestID int, message string, want codes.Code) {   
ctx, cancel := context.WithTimeout(context.Background(), time.Second) //超时设置   
defer cancel()    req := &pb.EchoRequest{Message: message} //参数部分 _, err := c.UnaryEcho(ctx, req) //调用函数发送请求给服务端
got := status.Code(err)   //
fmt.Printf("[%v] wanted = %v, got = %v\n", requestID, want, got)
}

上面的code设置在文件 grpc/codes/codes.go

type Code uint32

const (
OK Code = 0
Canceled Code = 1
Unknown Code = 2
InvalidArgument Code = 3
DeadlineExceeded Code = 4
... ...
)

2端都是stream的函数:

// streamingCall,2端都是stream
func streamingCall(c pb.EchoClient, requestID int, message string, want codes.Code) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)//超时设置
defer cancel() stream, err := c.BidirectionalStreamingEcho(ctx)//双向stream
if err != nil {
log.Printf("Send error : %v", err)
return
} err = stream.Send(&pb.EchoRequest{Message: message})//发送
if err != nil {
log.Printf("Send error : %v", err)
return
} _, err = stream.Recv() //接收 got := status.Code(err)
fmt.Printf("[%v] wanted = %v, got = %v\n", requestID, want, got)
}

main 执行函数

func main() {
flag.Parse() conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect : %v ", err)
}
defer conn.Close() c :=pb.NewEchoClient(conn) // 成功请求
unaryCall(c, 1, "word", codes.OK)
// 超时 deadline
unaryCall(c, 2, "delay", codes.DeadlineExceeded)
// A successful request with propagated deadline
unaryCall(c, 3, "[propagate me]world", codes.OK)
// Exceeds propagated deadline
unaryCall(c, 4, "[propagate me][propagate me]world", codes.DeadlineExceeded)
// Receives a response from the stream successfully.
streamingCall(c, 5, "[propagate me]world", codes.OK)
// Exceeds propagated deadline before receiving a response
streamingCall(c, 6, "[propagate me][propagate me]world", codes.DeadlineExceeded)
}

3.服务端

定义一个struct

type server struct {   
pb.UnimplementedEchoServer   
client pb.EchoClient   
cc *grpc.ClientConn
}

2端不是stream的函数:

func (s *server) UnaryEcho(ctx context.Context, req *pb.EchoRequest)(*pb.EchoResponse, error) {
message := req.Message
if strings.HasPrefix(message, "[propagate me]") {//判断接收的值
time.Sleep(800 * time.Millisecond)
message := strings.TrimPrefix(message, "[propagate me]")
return s.client.UnaryEcho(ctx, &pb.EchoRequest{Message:message}) //<1>
} if message == "delay" {
time.Sleep(1500 * time.Millisecond) // message=delay 时睡眠1500毫秒,大于client的设置的1秒,这里就超时了
} return &pb.EchoResponse{Message:message}, nil
}

上面函数标注 <1> 这个地方比较有意思,当client端发送的字符串包含 [propagate me] 字符串时,先睡眠800毫秒,然后在重新执行客户端请求服务端的函数 s.client.UnaryEcho() , 在次运行到服务端的 UnaryEcho(),客户端已经超时了。

也就是说client/main.go 先请求了一次服务端,然后在server/main.go 的函数 func (s *server) UnaryEcho(ctx context.Context, req *pb.EchoRequest) 又执行了一次请求服务端,所以会导致超时。

2端设置stream的函数:

func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
for {
req, err := stream.Recv()
if err == io.EOF {
return status.Error(codes.InvalidArgument, "request message not received")
}
if err != nil {
return err
} message := req.Message
if strings.HasPrefix(message, "[propagate me]") {
time.Sleep(800 * time.Millisecond)
message = strings.TrimPrefix(message, "[propagate me]")
res, err := s.client.UnaryEcho(stream.Context(), &pb.EchoRequest{Message:message})//再次执行客户端请求服务端函数,这里可能会超时
if err != nil {
return err
}
stream.Send(res)
} if message == "delay" {
time.Sleep(1500 * time.Millisecond)
}
stream.Send(&pb.EchoResponse{Message:message})
}
}

main函数

func main() {
flag.Parse() address := fmt.Sprintf(":%v", *port)
lis, err := net.Listen("tcp", address)
if err != nil {
log.Fatalf("failed to listen: %v ", err)
} echoServer := newEchoServer()
defer echoServer.Close() grpcServer := grpc.NewServer()
pb.RegisterEchoServer(grpcServer, echoServer) if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v ", err)
}
}

执行

先运行 /server/main.go , go run main.go

在运行 /client/main.go, go run main.go

执行结果:

 go run main.go
[1] wanted = OK, got = OK
[2] wanted = DeadlineExceeded, got = DeadlineExceeded
[3] wanted = OK, got = Unavailable
[4] wanted = DeadlineExceeded, got = Unavailable
[5] wanted = OK, got = Unavailable
[6] wanted = DeadlineExceeded, got = Unavailable

gRPC 系列代码地址:

参考:

Golang gRPC学习(04): Deadlines超时限制的更多相关文章

  1. golang——gRPC学习

    1.获取gRPC 环境变量GOPATH的src目录下执行: git clone https://github.com/grpc/grpc-go.git google.golang.org/grpc g ...

  2. Golang gRPC学习(03): grpc官方示例程序route_guide简析

    代码主要来源于grpc的官方examples代码: route_guide https://github.com/grpc/grpc-go/tree/master/examples/route_gui ...

  3. gRPC学习之三:初试GO版gRPC开发

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. gRPC学习之四:实战四类服务方法

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. gRPC学习之一:在CentOS7部署和设置GO

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. gRPC学习之二:GO的gRPC开发环境准备

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos gRPC学习系列文章链接 在CentOS7部署和设置G ...

  7. gRPC学习之五:gRPC-Gateway实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. Python学习--04条件控制与循环结构

    Python学习--04条件控制与循环结构 条件控制 在Python程序中,用if语句实现条件控制. 语法格式: if <条件判断1>: <执行1> elif <条件判断 ...

  9. Golang 语法学习笔记

    Golang 语法学习笔记 包.变量和函数. 包 每个 Go 程序都是由包组成的. 程序运行的入口是包 main. 包名与导入路径的最后一个目录一致."math/rand" 包由 ...

随机推荐

  1. 如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比

    简述 上一篇简述了ABP框架中的一些基础理论,包括ABP前后端项目的分层结构,以及后端项目中涉及到的知识点,例如DTO,应用服务层,整洁架构,领域对象(如实体,聚合,值对象)等. 笔者也曾经提到,AB ...

  2. 【揭秘】C语言类型转换时发生了什么?

    ID:技术让梦想更伟大 作者:李肖遥 链接:https://mp.weixin.qq.com/s/ZFf3imVaJgeesuhl1Kn9sQ 在C语言中,数据类型指的是用于声明不同类型的变量或函数的 ...

  3. 使用ImpromptuInterface反射库方便的创建自定义DfaGraphWriter

    在本文中,我为创建的自定义的DfaGraphWriter实现奠定了基础.DfaGraphWriter是公开的,因此您可以如上一篇文章中所示在应用程序中使用它,但它使用的所有类均已标记为internal ...

  4. 图文详解压力测试工具JMeter的安装与使用

    压力测试是目前大型网站系统的设计和开发中不可或缺的环节,通常会和容量预估等工作结合在一起,穿插在系统开发的不同方案.压力测试可以帮助我们及时发现系统的性能短板和瓶颈问题,在这个基础在上再进行针对性的性 ...

  5. time strptime()方法 时间操作

    Python time strptime()方法 时间操作   描述 Python time strptime() 函数根据指定的格式把一个时间字符串解析为时间元组. 语法 strptime()方法语 ...

  6. Tomcat内部结构、工作原理、工作模式和运行模式

    TOMCAT的内部结构 Tomcat是一个基于组件的服务器,它的构成组件都是可配置的,其中最外层的是Catalina servlet容器,其他组件按照一定的格式要求配置在这个顶层容器中.Tomcat的 ...

  7. Redis 和 memcache 简单比较

    1.Redis不仅仅支持简单的key-value类型的数据,同时还提供list.set.zset.hash等数据结构的存储. 2.Redis支持master-slave(主--从)模式应用. 3.Re ...

  8. 关于RecyclerView(一)基本使用

    前言 最近在项目中用到了ListView,不知道为什么总是出现数据错乱的情况,另外RecyclerView包含很多Item动画,所以改成了RecyclerView. 简单使用 RecyclerView ...

  9. Android个人中心UI

    参考:https://blog.csdn.net/gjm15881133824/article/details/73742219

  10. PHP array_combine() 函数

    ------------恢复内容开始------------ 实例 通过合并两个数组来创建一个新数组,其中的一个数组元素为键名,另一个数组元素为键值: <?php$fname=array(&qu ...