Grpc 高级特性之 超时机制 &状态码定义

13 篇文章 1 订阅

GRPC 超时机制

超时介绍

一般而言,在微服务架构下,客户端通过设置合理的调用超时时间在系统性能、服务运维层面取一个折中。超时时间设置太短,在服务端正常突发的压力下,可能获取不到正常的结果;超时时间设置太长,极端情况下(网络延迟),则可能处于一直等待状态. gRPC 中设置超时时间有两种概念:

  • timeout 针对单个rpc调用 客户端设置等待超时时间

  • deadline 针对微服务调用链路 在最开始调用的地方设置

    在不考虑网络延迟的情况下,整个微服务调用链路的deadline截止时间 应该是所有客户端 超时时间之和

在这里插入图片描述

如上图,客户端调用商品服务需要3S, 商品服务调用库存服务设置5S超时,那么整个链路的调用截止时间就是8S.

Example 代码

客户端-核心代码

  • 设置超时时间
//设置5秒超时
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
  • 设置Deadline时间
//设置5秒调用截止
clientDeadline := time.Now().Add(time.Duration(1000) * time.Millisecond * 5)
ctx, cancel := context.WithDeadline(context.Background(), clientDeadline)

服务端-核心代码

针对客户端设置的超时请求(timeout、deadline),服务端都需要进行超时检测,并进行对应的处理代码如下

time.Sleep(time.Second * 10)
log.Printf("Received: %v", in.GetName())
// 网上说这种方式判断是否取消,但是测试不成功 还希望熟悉的大神 指导下
//if ctx.Err() == context.Canceled {
if ctx.Err() != nil {
  log.Printf("request canceled %v", in.GetName())
  return nil, status.Errorf(codes.Internal, "Unexpected error from context packet: %v", ctx.Err())
}

代码测试

  • 服务端完整代码

    package main
    
    import (
    	"context"
    	"flag"
    	"fmt"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/codes"
    	pb "google.golang.org/grpc/examples/helloworld/helloworld"
    	"google.golang.org/grpc/status"
    	"log"
    	"net"
    	"time"
    )
    
    var (
    	port = flag.Int("port", 50051, "The server port")
    )
    
    // server is used to implement helloworld.GreeterServer.
    type server struct {
    	pb.UnimplementedGreeterServer
    }
    
    // SayHello implements helloworld.GreeterServer
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    	time.Sleep(time.Second * 10)
    	log.Printf("Received: %v", in.GetName())
    	// 判断context上下文是否超时 状态
    	// 网上说这种方式判断是否取消,但是测试不成功 还希望熟悉的大神 指导下
    	//if ctx.Err() == context.Canceled {
    	if ctx.Err() != nil {
    		log.Printf("request canceled %v", in.GetName())
    		return nil, status.Errorf(codes.Internal, "Unexpected error from context packet: %v", ctx.Err())
    	}
    	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
    }
    
    func main() {
    	flag.Parse()
    	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	s := grpc.NewServer()
    	pb.RegisterGreeterServer(s, &server{})
    	log.Printf("server listening at %v", lis.Addr())
    	if err := s.Serve(lis); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    
  • 客户端完整代码

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    	"time"
    
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    	pb "google.golang.org/grpc/examples/helloworld/helloworld"
    )
    
    const (
    	defaultName = "world"
    )
    
    var (
    	addr = flag.String("addr", "localhost:50051", "the address to connect to")
    	name = flag.String("name", defaultName, "Name to greet")
    )
    
    func main() {
    	flag.Parse()
    	// Set up a connection to the 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)
    
    	// Contact the server and print out its response.
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
    
    	//clientDeadline := time.Now().Add(time.Duration(1000) * time.Millisecond * 5)
    	//ctx, cancel := context.WithDeadline(context.Background(), clientDeadline)
    
    	defer cancel()
    	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    	if err != nil {
    		log.Fatalf("could not greet: %v", err)
    	}
    	log.Printf("Greeting: %s", r.GetMessage())
    }
    
    • 测试结果

      先启动服务端,后启动客户端,客户端输出结果

      2022/11/06 17:02:26 could not greet: rpc error: code = DeadlineExceeded desc = context deadline exceeded
      

HTTP2 超时协议

到目前为止我们已经完成了grpc 超时功能的开发,包含客户端设置、服务端检测。现在来通过抓包工具看一下客户端设置的超时数据是如何传递给服务端的。

抓包工具使用Wireshark,至于如何使用请参考另一篇blog,Grpc Quick Start 之协议分析

在这里插入图片描述

如上图,客户端设置的超时时间为5秒,grpc协议将超时时间放置在HTTP Header 请求头里面

grpc-timeout: 4995884u

其中u表示时间单位为纳秒,4995884u 约等于 5秒。然后服务端接收到该请求后,就可以根据这个时间计算出是否超时,由header 超时设置,接下来衍生一下grpc在HTTP2 Header里面自定义了多少消息头 ,以及它们的应用场景。

grpc headers

该部分介绍通过HTTP2通讯协议来实现gRPC的实现。如元数据定义、header新增,通过Request、Response两部分来介绍。

Http Request Header

gRPC在Request 请求头自定义了以下元数据已满足通讯的要求

  • Method → 指定请求类型

  • Scheme → 请求模式 (“http” / “https”)

  • Path → 请求URI “:path” “/” Service-Name “/” {method name}

  • Service-Name → IDL特定服务名称

  • Authority → “认证信息” 一般为请求host + 端口

  • TE → 通常用于检测不兼容的代码,值为: “te” “trailers”

  • Timeout → grpc 超时header

  • TimeoutValue →grpc 超时数值 一般为正整数,最多8个数字表示

  • TimeoutUnit → 超时单位,支持小时/分钟/秒/毫秒/微妙/纳秒,用一个字符表示

    • Hour → “H”

    • Minute → “M”

    • Second → “S”

    • Millisecond → “m”

    • Microsecond → “u” 默认使用微妙表示超时时间

    • Nanosecond → “n”

  • Content-Type → “content-type” “application/grpc” 请求类型

  • Content-Coding → “identity” / “gzip” / “deflate” / “snappy” / {custom} 数据压缩方式

  • Message-Encoding → “grpc-encoding” 消息编码方式

  • Message-Accept-Encoding → “grpc-accept-encoding” Content-Coding *(“,” Content-Coding)

  • User-Agent → “user-agent” 用户代理

  • Message-Type → “grpc-message-type” 消息类型

  • Custom-Metadata → Binary-Header / ASCII-Header 自定义元数据

  • Binary-Header → {Header-Name “-bin” } 表示二进制传输

  • ASCII-Header → Header-Name ASCII-Value 表示ASCII码传输

  • Header-Name → 1*( %x30-39 / %x61-7A / “_” / “-” / “.”) ; 0-9 a-z _ - .

  • ASCII-Value → 1*( %x20-%x7E ) ; 空格、可打印的ASCII码

Http Response Header

  • Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only
  • Response-Headers → HTTP-Status [Message-Encoding] [Message-Accept-Encoding] Content-Type *Custom-Metadata
  • Trailers-Only → HTTP-Status Content-Type Trailers
  • Trailers → Status [Status-Message] *Custom-Metadata
  • HTTP-Status → “:status 200”
  • Status → “grpc-status”
  • Status-Message → “grpc-message” Percent-Encoded
  • Percent-Encoded → 1*(Percent-Byte-Unencoded / Percent-Byte-Encoded)
  • Percent-Byte-Unencoded → 1*( %x20-%x24 / %x26-%x7E ) ; space and VCHAR, except %
  • Percent-Byte-Encoded → “%” 2HEXDIGIT ; 0-9 A-F

grpc-status 定义

CodeNumberDescription
OK0没有错误 成功返回
CANCELLED1客户端取消调用
UNKNOWN2未知异常
INVALID_ARGUMENT3非法参数
DEADLINE_EXCEEDED4调用超时时间异常
NOT_FOUND5实体未找到;某些访问拒绝的场景也可以使用该异常
ALREADY_EXISTS6客户端创建的实体已存在
PERMISSION_DENIED7非法访问
RESOURCE_EXHAUSTED8资源耗尽
FAILED_PRECONDITION9操作被拒绝
ABORTED10操作终止
OUT_OF_RANGE11超出合法范围
UNIMPLEMENTED12操作不支持
INTERNAL13内部错误
UNAVAILABLE14服务不可用
DATA_LOSS15数据丢失
UNAUTHENTICATED16未认证

在这里插入图片描述

如上图,在gRPC Response 返回的信息中 已经没有 熟知的HTTP status 200的状态了,取而代之的是grpc-status:0 表示正常的返回。个人猜测应该是HTTP STATUS 状态码在某些特定的场景下 不能满足gRPC的要求,如INVALID_ARGUMENT、DEADLINE_EXCEEDED。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GRPC请求处理时间超时设置可以通过在创建`ManagedChannel`或`Server`时设置`ClientInterceptors`或`ServerInterceptors`来实现。 在客户端,您可以使用`ManagedChannelBuilder`创建一个`ManagedChannel`对象,并通过`withDeadlineAfter`方法设置请求处理时间的超时时间。例如: ```java ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .intercept(ClientInterceptors.intercept(new YourClientInterceptor())) .build(); // 设置请求处理时间超时为5秒 channel.withDeadlineAfter(5, TimeUnit.SECONDS); ``` 在这个例子中,我们使用`ClientInterceptors.intercept`方法添加了一个客户端拦截器`YourClientInterceptor`,并通过`withDeadlineAfter`方法将请求处理时间的超时设置为5秒。 在服务器端,您可以使用`ServerBuilder`创建一个`Server`对象,并通过`ServerInterceptors`设置请求处理时间的超时时间。例如: ```java Server server = ServerBuilder.forPort(port) .addService(new YourService()) .intercept(ServerInterceptors.intercept(new YourServerInterceptor())) .build(); // 设置请求处理时间超时为5秒 server = server.withDeadlineAfter(5, TimeUnit.SECONDS); ``` 在这个例子中,我们使用`ServerInterceptors.intercept`方法添加了一个服务器拦截器`YourServerInterceptor`,并通过`withDeadlineAfter`方法将请求处理时间的超时设置为5秒。 请注意,您需要在客户端和服务器端都设置超时时间,以确保请求在超时时间内完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值