GRPC通过Context上下文跨进程传输全局数据

前言

  在go语言中没有设计类似Java的ThreadLocal机制(就是把数据跟线程进行绑定,在进程的任何地方都能很方便的拿到数据),为了方便、安全的在多个GoRoutine之间传递数据,go语言为我们提供了context包,所以大部分情况通过参数传递context上下文方式来共享数据

跨进程获取全局数据

  我们还是用context.withValue的方式把数据放入context,然后使用grpc的方式进行远程传输

客户端代码

func (l *GetUserInfoLogic) GetUserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
	d := new(depositservice.DepositRequest)
	context := context.WithValue(l.ctx, "user", "葫芦娃")
	deposit, err := l.svcCtx.DepositServiceRpc.Deposit(context, d)
	fmt.Println(deposit)
	return nil, errors.New("中国人")
}

服务端代码

func (s *DepositServiceServer) Deposit(ctx context.Context, in *mock.DepositRequest) (*mock.DepositResponse, error) {
	a, b := logic.NewAwsMsgLogic(ctx, s.svcCtx).GetById("0001e67c-a610-4a73-9404-9ca223e67cc5")
	value := ctx.Value("user")
	fmt.Println(value)
	fmt.Println(a, b)
	return &mock.DepositResponse{}, nil
}

服务端结果

在这里插入图片描述
  根据结果可以看出context.withValue不能使用在跨进程传输的场景,那么如何实现跨进程进行全局数据传输,比如我们的登录用户信息、链路追踪的traceId等很多业务场景都需要这种功能,个人觉得go官方设计grpc的时候不可能没考虑到上下文远程传输的场景
  GRPC是基于HTTP/2协议的,可以通过请求头来进行传输上下文数据,与是查阅了相关资料发现grpc确实是这样实现的,它在进程间传输定义了一个metadata对象,并把该对象放在了http请求头Custom-Metadata中。具体文档可以看这里:PROTOCOL-HTTP2

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

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

对于接收方来说,无非就是解析 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() 方法遥相呼应。

客户端代码正确姿势

func (l *GetUserInfoLogic) GetUserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
	d := new(depositservice.DepositRequest)
	context := metadata.NewOutgoingContext(l.ctx, metadata.Pairs("user", url.QueryEscape("葫芦娃")))
	deposit, err := l.svcCtx.DepositServiceRpc.Deposit(context, d)
	fmt.Println(deposit)
	return nil, errors.New("中国人")
}

注意http头传输中文数据必须进行编码,否者报错

服务端代码正确姿势

func (s *DepositServiceServer) Deposit(ctx context.Context, in *mock.DepositRequest) (*mock.DepositResponse, error) {
	a, b := logic.NewAwsMsgLogic(ctx, s.svcCtx).GetById("0001e67c-a610-4a73-9404-9ca223e67cc5")
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		fmt.Println(url.QueryUnescape(md["user"][0]))
	}

	fmt.Println(a, b)
	return &mock.DepositResponse{}, nil
}

服务端输出内容

API server listening at: 127.0.0.1:58060

2023/10/12 15:21:37 D:/Program Files/Go/IdeaProject/gozero/server/internal/model/aws_msg_model_gen.go:89
[49.798ms] [rows:0] SELECT * FROM `aws_msg` WHERE id='0001e67c-a610-4a73-9404-9ca223e67cc5' AND `aws_msg`.`deleted_at` IS NULL
葫芦娃 <nil>
&{       0001-01-01 00:00:00 +0000 UTC} <nil>

Debugger finished with the exit code 0

可以看出输出内容达到我们想要的效果

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

王柳敬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值