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

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

跨进程的全局数据传输

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

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

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

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

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

type MD map[string][]string

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

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

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

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

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

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

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Printf("get metadata error")
}
if t, ok := md["key"]; ok {
fmt.Printf("key from metadata:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
}

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

跨进程的超时停止

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

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

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

服务端:

package normal

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "gorm-demo/models/pb"
"net"
"testing"
"time"
) type server struct{} func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
time.Sleep(3 * time.Second)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
} //拦截器 - 打印日志
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
fmt.Printf("gRPC method: %s, %v", info.FullMethod, req)
resp, err := handler(ctx, req)
fmt.Printf("gRPC method: %s, %v", info.FullMethod, resp)
return resp, err
} func TestGrpcServer(t *testing.T) {
// 监听本地的8972端口
lis, err := net.Listen("tcp", ":8972")
if err != nil {
fmt.Printf("failed to listen: %v", err)
return
}
//注册拦截器
s := grpc.NewServer(grpc.UnaryInterceptor(LoggingInterceptor)) // 创建gRPC服务器
pb.RegisterGreeterServer(s, &server{}) // 在gRPC服务端注册服务 reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
// Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
// 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
err = s.Serve(lis)
if err != nil {
fmt.Printf("failed to serve: %v", err)
return
} }

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

package normal

import (
"fmt"
"testing"
"time" "golang.org/x/net/context"
"google.golang.org/grpc"
pb "gorm-demo/models/pb"
) func TestGrpcClient(t *testing.T) {
// 连接服务器
conn, err := grpc.Dial(":8972", grpc.WithInsecure())
if err != nil {
fmt.Printf("faild to connect: %v", err)
}
defer conn.Close() c := pb.NewGreeterClient(conn) //timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*2)
//defer cancelFunc() m, _ := time.ParseDuration("1s")
result := time.Now().Add(m)
deadline, c2 := context.WithDeadline(context.Background(), result)
defer c2() // 调用服务端的SayHello
r, err := c.SayHello(deadline, &pb.HelloRequest{Name: "CN"})
if err != nil {
fmt.Printf("could not greet: %v", err)
} fmt.Printf("Greeting: %s !\n", r.Message)
}

针对两种场景的超时:

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

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

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. TensorFlow文本情感分析实现

    TensorFlow文本情感分析实现 前面介绍了如何将卷积网络应用于图像.本文将把相似的想法应用于文本. 文本和图像有什么共同之处?乍一看很少.但是,如果将句子或文档表示为矩阵,则该矩阵与其中每个单元 ...

  2. MindSpore数据集mindspore::dataset

    MindSpore数据集mindspore::dataset ResizeBilinear #include <image_process.h> bool ResizeBilinear(L ...

  3. 车联网V-2X智能汽车驾驶

    车联网V-2X智能汽车驾驶 早期的功能互联汽车无法满足全球车主针对不同应用和定制移动服务的各种需求.这导致较低的客户续订率,较高的建造和运营成本以及较低的组装率.通常,在没有统一平台的情况下,不同的车 ...

  4. SQL进阶总结(二)

    2.第二个特性----以集合为单位进行操作 在我们以往面向过程语言不同,SQL是一门面向集合的一门语言.由于习惯了面向过程的思考方式,导致我们在使用SQL时往往也陷入之前的思维定式. 我们现在分别创建 ...

  5. 【NX二次开发】Block UI 曲线收集器

    属性说明 属性   类型   描述   常规           BlockID    String    控件ID    Enable    Logical    是否可操作    Group    ...

  6. Kafka 的这些原理你懂吗

    如果只是为了开发 Kafka 应用程序,或者只是在生产环境使用 Kafka,那么了解 Kafka 的内部工作原理不是必须的.不过,了解 Kafka 的内部工作原理有助于理解 Kafka 的行为,也利用 ...

  7. java并发编程JUC第十二篇:AtomicInteger原子整型

    AtomicInteger 类底层存储一个int值,并提供方法对该int值进行原子操作.AtomicInteger 作为java.util.concurrent.atomic包的一部分,从Java 1 ...

  8. 关于Linux服务器部署

    服务器信息: 此小节的内容: SecurityCRT:用来连接到Linux服务器命令操作. FTP(FTPRush):本地文件和Linux服务器文件交互的 工具服务器 借助客户端工具来链接到Linux ...

  9. Linux查看与设定别名

    1.alias :查看系统中所有的命令别名 2.设定别名 alias 别名='原命令' 3.删除别名 unalias 别名 4.使别名永久生效    vi  ~/.bashrc  写入这个文件中即可永 ...

  10. 向虚拟机注册钩子,实现Bean对象的初始化和销毁方法

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 有什么方式,能给代码留条活路? 有人说:人人都是产品经理,那你知道吗,人人也都可以是 ...