gRPC中的metadata(Golang)

9/14/2022

metadata,简称MD,一般用来传递消息以外的额外数据(挂载数据):

  • 链路追踪的参数,如traceId spanId等
  • 错误的详情
  • 签名校验
  • 其他通用参数

# 一、理解metadata

gRPC是基于HTTP2的,HTTP2中除了 header 还有 trailer。

metadata就是放在header和trailer中传输的

客户端发起请求的时候可以带,服务端返回数据的时候也可以带。

metadata是一个key-value结构的数据(map),但是value是string类型的切片

type MD map[string][]string

# header和trailer

两者都是放在http的头信息里的,但gRPC中,只有服务端返回的trailer可以带数据

客户端发送请求的MD都会放到header中

服务端响应的MD可以选择放到header,也可以选择放到trailer

对于Unary模式的调用,因为是一次请求一次响应,所以放在哪里无所谓。

但是stream模式下的调用,客户端会先接到header的包,然后才会收到trailer的包

# 二、构造metadata

client可以直接创建一个metadata。

// key1 的值将会是一个slice,有两个值: []string{"val1", "val1-2"}
md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2",
    "key2", "val2",
)

所有的key都会自动转为小写,所以 key1 和 Key1是一样的

微服务中的链路追踪所依赖的traceId,就是在metadata中一路传递的。 因而服务端的md往往不会直接创建,而是使用 FromIncomingContext 创建

//从请求的ctx中获取md
md, ok := metadata.FromIncomingContext(ctx)

# 三、客户端发送和接收MD

# 1.发送

gRPC调用中,client 通过将 MD添加到 context.Context 中,送给服务端。

  • 可通过 NewOutgoingContext 创建一个带md的context
  • 可以 AppendToOutgoingContext 对原有的 context 追加参数
  • 可以使用 metadata.Join 对ctx追加
//创建一个带md的context
md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
ctx := metadata.NewOutgoingContext(context.Background(), md)

//对原有的 context 追加,AppendToOutgoingContext 
ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")

//对原有的 context 追加参数,metadata.Join
send, _ := metadata.FromOutgoingContext(ctx)
newMD := metadata.Pairs("k3", "v3")
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(send, newMD))

NewOutgoingContext 会清空原本的所有 metadata,使用时需要注意。

而且AppendToOutgoingContext的操作会更快一些

# 2.接收

对于Unary模式的调用,可以在 callOption中绑定用于接收的变量。

// 提前声明用于接收的变量
var header, trailer metadata.MD
r, err := client.SomeRPC(
    ctx,
    someRequest,
    grpc.Header(&header),    // 接收的header放在这里
    grpc.Trailer(&trailer),  // 接收的trailer放这里
)

对于流模式的调用,可以通过返回的stream直接获取。但是header会先于trailer返回

stream, err := client.SomeStreamingRPC(ctx)

// 取回 header,
header, err := stream.Header()

// 取回 trailer
trailer := stream.Trailer()

# 四、服务端接收和发送MD

# 1. 接收

服务端也是从ctx中获取

//unary 模式
md, ok := metadata.FromIncomingContext(ctx)
// stream模式
md, ok := metadata.FromIncomingContext(stream.Context())

# 2. 发送

服务端的发送,其实就是响应中带MD。

可以选择填充进响应的header,也可以填充进响应的trailer。

# Unary模式下

func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
    // 创建并设置 header
    header := metadata.Pairs("header-key", "val")
    grpc.SendHeader(ctx, header)
    // 创建并设置 trailer
    trailer := metadata.Pairs("trailer-key", "val")
    grpc.SetTrailer(ctx, trailer)
}

# Stream模式下

func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
    // 创建并设置 header
    header := metadata.Pairs("header-key", "val")
    stream.SendHeader(header)
    // 创建并设置 trailer
    trailer := metadata.Pairs("trailer-key", "val")
    stream.SetTrailer(trailer)
}