前言
在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
可以看出输出内容达到我们想要的效果