gRPC 在多个 GoRoutine 之间传递数据使用的是 Go SDK 提供的 Context 包。关于 Context 的使用可以看我之前的一篇文章:Context 使用

但是 Context 的使用场景是同一个进程内,gRPC 使用都是跨进程的网络传输,如果在某个调用链上 A 服务当前要调用 B 服务传递一些上下文参数并且也希望 B 服务继续往下传递该如何实现呢?

跨进程的全局数据传输

再次回忆一下 gRPC 是基于 HTTP/2 协议的。那我们是不是可以再请求头中将这一部分数据 set 进去,而不是放在数据包里面。

gRPC 也是如此实现的。进程间传输定义了一个 metadata 对象,该对象放在 Request-Headers 内:

  1. Requests
  2. Request Request-Headers *Length-Prefixed-Message EOS
  3. Request-Headers are delivered as HTTP2 headers in HEADERS + CONTINUATION frames.
  4. Request-Headers Call-Definition *Custom-Metadata
  5. Call-Definition Method Scheme Path TE [Authority] [Timeout] Content-Type [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]
  6. Method ":method POST"
  7. Scheme ":scheme " ("http" / "https")
  8. Path ":path" "/" Service-Name "/" {method name} # But see note below.
  9. Service-Name {IDL-specific service name}
  10. ......
  11. ......
  12. ......
  13. Custom-Metadata Binary-Header / ASCII-Header
  14. ......

Custom-Metadata 字段内即为我们要传输的全局对象。具体文档可以看这里:PROTOCOL-HTTP2

所以通过 metadata 我们可以将上一个进程中的全局对象透传到下一个被调用的进程。查看源码可以发现 metadata 内部实际上是通过一个 map 对象存储数据:

  1. type MD map[string][]string

metadata 和 Context 一起连用的使用方式如下:

发送方如果想发送一些全局字段给接收方,首先从自己端的 metadata set 数据:

  1. //set 数据到 metadata
  2. md := metadata.Pairs("key", "val")
  3. // 新建一个有 metadata 的 context
  4. ctx := metadata.NewOutgoingContext(context.Background(), md)

注意上面的 NewOutgoingContext() 方法,命名很形象,向外输出 Context。那么对端接收的时候肯定有一个对应的方法,我们继续往下看。这个新的 Context 就可以用来发送出去,比如还是我们上文中的示例方法:

  1. //set 数据到 metadata
  2. md := metadata.Pairs("key", "val")
  3. // 新建一个有 metadata 的 context
  4. ctx := metadata.NewOutgoingContext(context.Background(), md)
  5. c = NewTokenServiceClient(conn)
  6. hello, err := c.SayHello(ctx, &PingMessage{Greeting: "hahah"})
  7. if err != nil {
  8. fmt.Printf("could not greet: %v", err)
  9. }

对于接收方来说,无非就是解析 metadata 中的数据。gRPC 已经帮我们将数据解析到 context 中,所以需要从 Context 中取出 MD 对象。

  1. md, ok := metadata.FromIncomingContext(ctx)
  2. if !ok {
  3. fmt.Printf("get metadata error")
  4. }
  5. if t, ok := md["key"]; ok {
  6. fmt.Printf("key from metadata:\n")
  7. for i, e := range t {
  8. fmt.Printf(" %d. %s\n", i, e)
  9. }
  10. }

这里取数的逻辑使用了 metadata 的 FromIncomingContext() 方法。跟存数据的 NewOutgoingContext() 方法遥相呼应。

跨进程的超时停止

同进程下跨 Goroutine 我们还是可以使用 Context 来设置当前 Context 管理下子 Goroutine 的有效期:

  1. //超时截止
  2. context.WithTimeout(context.Background(), 100*time.Millisecond)
  3. //限制截止
  4. deadline, c2 := context.WithDeadline(context.Background(), deadline time.Time)

gRPC 中同样实现了这个功能,即跨进程间的 Context 传递实现进程间的 Context 生命周期管理。我们看一个简单的例子:

服务端:

  1. package normal
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc"
  6. "google.golang.org/grpc/reflection"
  7. pb "gorm-demo/models/pb"
  8. "net"
  9. "testing"
  10. "time"
  11. )
  12. type server struct{}
  13. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  14. time.Sleep(3 * time.Second)
  15. return &pb.HelloReply{Message: "Hello " + in.Name}, nil
  16. }
  17. //拦截器 - 打印日志
  18. func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
  19. handler grpc.UnaryHandler) (interface{}, error) {
  20. fmt.Printf("gRPC method: %s, %v", info.FullMethod, req)
  21. resp, err := handler(ctx, req)
  22. fmt.Printf("gRPC method: %s, %v", info.FullMethod, resp)
  23. return resp, err
  24. }
  25. func TestGrpcServer(t *testing.T) {
  26. // 监听本地的8972端口
  27. lis, err := net.Listen("tcp", ":8972")
  28. if err != nil {
  29. fmt.Printf("failed to listen: %v", err)
  30. return
  31. }
  32. //注册拦截器
  33. s := grpc.NewServer(grpc.UnaryInterceptor(LoggingInterceptor)) // 创建gRPC服务器
  34. pb.RegisterGreeterServer(s, &server{}) // 在gRPC服务端注册服务
  35. reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
  36. // Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
  37. // 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
  38. err = s.Serve(lis)
  39. if err != nil {
  40. fmt.Printf("failed to serve: %v", err)
  41. return
  42. }
  43. }

服务端代码我们在 SayHello() 方法中增加了 3s 的sleep。客户端代码如下:

  1. package normal
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. "golang.org/x/net/context"
  7. "google.golang.org/grpc"
  8. pb "gorm-demo/models/pb"
  9. )
  10. func TestGrpcClient(t *testing.T) {
  11. // 连接服务器
  12. conn, err := grpc.Dial(":8972", grpc.WithInsecure())
  13. if err != nil {
  14. fmt.Printf("faild to connect: %v", err)
  15. }
  16. defer conn.Close()
  17. c := pb.NewGreeterClient(conn)
  18. //timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*2)
  19. //defer cancelFunc()
  20. m, _ := time.ParseDuration("1s")
  21. result := time.Now().Add(m)
  22. deadline, c2 := context.WithDeadline(context.Background(), result)
  23. defer c2()
  24. // 调用服务端的SayHello
  25. r, err := c.SayHello(deadline, &pb.HelloRequest{Name: "CN"})
  26. if err != nil {
  27. fmt.Printf("could not greet: %v", err)
  28. }
  29. fmt.Printf("Greeting: %s !\n", r.Message)
  30. }

针对两种场景的超时:

  1. //timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*2)
  2. //defer cancelFunc()
  3. m, _ := time.ParseDuration("1s")
  4. result := time.Now().Add(m)
  5. deadline, c2 := context.WithDeadline(context.Background(), result)
  6. defer c2()

分别做了测试,大家可以运行一下代码看看效果。都会看到报错信息:

  1. code = DeadlineExceeded desc = context deadline exceeded

所以超时控制可以通过 Context 来操作,不必你自己再去额外写代码。

跟我一起学Go系列:gRPC 全局数据传输和超时处理的更多相关文章

  1. 跟我一起学 Go 系列:gRPC 拦截器

    Go gRPC 学习系列: 跟我一起学Go系列:gRPC 入门必备 第一篇内容我们已经基本了解到 gRPC 如何使用 .对应的三种流模式.现在已经可以让服务端和客户端互相发送消息.本篇仍然讲解功能性的 ...

  2. 跟我一起学Go系列:Go gRPC 安全认证机制-SSL/TLS认证

    Go gRPC 系列: 跟我一起学Go系列:gRPC 拦截器使用 跟我一起学Go系列:gRPC 入门必备 第一篇入门说过 gRPC 底层是基于 HTTP/2 协议的,HTTP 本身不带任何加密传输功能 ...

  3. 跟我一起学Go系列:Go gRPC 安全认证方式-Token和自定义认证

    Go gRPC 系列: 跟我一起学Go系列:gRPC安全认证机制-SSL/TLS认证 跟我一起学 Go 系列:gRPC 拦截器使用 跟我一起学 Go 系列:gRPC 入门必备 接上一篇继续讲 gRPC ...

  4. .net基础学java系列(二)IDE

    上一篇文章.net基础学java系列(一)视野 废话: "视野"这篇文章,管理员说它比较空洞!也许初学者看不懂表格中的大部分内容!多年的neter估计也有很多不知道的! 有.net ...

  5. 三叔学FPGA系列之二:Cyclone V中的POR、配置、初始化,以及复位

    对于FPGA内部的复位,之前一直比较迷,这两天仔细研究官方数据手册,解开了心中的诸多疑惑,感觉自己又进步了呢..... 原创不易,转载请转原文,注明出处,谢谢.   一.关于POR(Power-On ...

  6. 跟着鸟哥学Linux系列笔记3-第11章BASH学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...

  7. 跟着鸟哥学Linux系列笔记2-第10章VIM学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 常用的文本编辑器:Emacs, pico, nano, joe, vim VI ...

  8. 跟着鸟哥学Linux系列笔记0-如何解决问题

    跟着鸟哥学Linux系列笔记0-扫盲之概念 在发生问题怎么处理: 1.  在自己的主机.网络数据库上查询How-To或FAQ -Linux 自身的文件数据: /usr/share/doc -CLDP中 ...

  9. 跟着鸟哥学Linux系列笔记1

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 装完linux之后,接下来一步就是进行相关命令的学习了 第五章:首次登录与在线求助man page 1. X ...

随机推荐

  1. 3D惯导Lidar仿真

    3D惯导Lidar仿真 LiDAR-Inertial 3D Plane Simulator 摘要 提出了最*点*面表示的形式化方法,并分析了其在三维室内同步定位与映射中的应用.提出了一个利用最*点*面 ...

  2. GPU加速计算

    GPU加速计算 NVIDIA A100 Tensor Core GPU 可针对 AI.数据分析和高性能计算 (HPC),在各种规模上实现出色的加速,应对极其严峻的计算挑战.作为 NVIDIA 数据中心 ...

  3. Nucleus SE RTOS初始化和启动

    Nucleus SE RTOS初始化和启动 Nucleus SE RTOS initialization and start-up 对于任何类型的操作系统,都有某种类型的启动机制.具体的工作方式因系统 ...

  4. python常识系列20-->python利用xlutils修改表格内容

    前言 世上的事,只要肯用心去学,没有一件是太晚的.要始终保持敬畏之心,对阳光,对美,对痛楚. 一.xlutils是什么? 是一个提供了许多操作修改excel文件方法的库: 属于python的第三方模块 ...

  5. flume采集MongoDB数据到Kafka中

    环境说明 centos7(运行于vbox虚拟机) flume1.9.0(自定义了flume连接mongodb的source插件) jdk1.8 kafka(2.11) zookeeper(3.57) ...

  6. 【NX二次开发】属性操作相关函数的使用方法

    内容包括:1.属性创建2.判断属性是否存在3.读取属性值4.时间属性转换成字符串5.统计属性的数量6.删除指定属性7.删除全部属性效果: 源码: #include <stdlib.h> # ...

  7. 【SQLite】教程01-SQLite简介与安装

    为什么要用 SQLite? 不需要一个单独的服务器进程或操作的系统(无服务器的). SQLite 不需要配置,这意味着不需要安装或管理. 一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘 ...

  8. NetCore Dapper封装

    一.前期环境准备 1.创建一个core webapi的项目,基于core3.1版本创建一个项目. 2.Dapper安装,使用NuGet来安装Dapper程序包 Install-Package Dapp ...

  9. 为什么有些公司的IT很乱?

    --别问,问就是赛马,问就是KPI驱动 为什么很多公司甚至是闻名遐迩的资深IT公司,都被吐槽IT技术建设很烂呢?按惯例,问为什么之前,先问是不是. ▒壹·鹅厂▒ 2018年一个名为"当下腾讯 ...

  10. NOIP模拟测试11「string&#183;matrix&#183;big」

    打的big出了点小问题,maxx初值我设的0然后少了10分 第二题暴力打炸 第一题剪了一些没用的枝依然40分 总分70 这是一次失败的考试 string 想到和序列那个题很像,但我没做序列,考场回忆学 ...