gRPC中的metadata(Golang)
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)
}