链路追踪Tracing Analysis为分布式应用的开发者提供了完整的调用链路还原、调用请求量统计、链路拓扑、应用依赖分析等工具。本文介绍如何通过Headers在ASM实现gRPC链路追踪。
示例工程
gRPC的示例工程请参见hello-servicemesh-grpc,本文档中提到的目录都为hello-servicemesh-grpc下的目录。
GRPC协议Headers编程实践
服务端获取Headers
- 基本方法
- 使用Java语言通过服务端获取Headers实现基本方法。
实现拦截器
ServerInterceptor
接口的interceptCall(ServerCall<ReqT, RespT> call,final Metadata m,ServerCallHandler<ReqT, RespT> h)
方法,通过String v = m.get(k)
获取header信息,get
方法入参类型为Metadata.Key<String>
。 - 使用Go语言通过服务端获取Headers实现基本方法。
metadata.FromIncomingContext(ctx)(md MD, ok bool)
,MD是一个map[string][]string
。 - 使用NodeJS语言通过服务端获取Headers实现基本方法。
call.metadata.getMap()
,返回值类型是[key: string]: MetadataValue
,MetadataValue
类型定义为string/Buffer
。 - 使用Python语言通过服务端获取Headers实现基本方法。
context.invocation_metadata()
,返回值类型为2-tuple数组,2-tuple的形式为('k','v')
,使用m.key, m.value
获取键值对。
- 使用Java语言通过服务端获取Headers实现基本方法。
- Unary RPC
- 使用Java语言通过服务端获取Headers实现Unary RPC。
对Headers无感知。
- 使用Go语言通过服务端获取Headers实现Unary RPC。
在方法中直接调用
metadata.FromIncomingContext(ctx)
,上下文参数ctx来自Talk的入参。 - 使用NodeJS语言通过服务端获取Headers实现Unary RPC。
在方法内直接调用
call.metadata.getMap()
。 - 使用Python语言通过服务端获取Headers实现Unary RPC。
在方法内直接调用
context.invocation_metadata()
。
- 使用Java语言通过服务端获取Headers实现Unary RPC。
- Server streaming RPC
- 使用Java语言通过服务端获取Headers实现Server streaming RPC。
对Headers无感知。
- 使用Go语言通过服务端获取Headers实现Server streaming RPC。
在方法中直接调用
metadata.FromIncomingContext(ctx)
,上下文参数ctx
从TalkOneAnswerMore的入参stream
中获取stream.Context()
。 - 使用NodeJS语言通过服务端获取Headers实现Server streaming RPC。
在方法内直接调用
call.metadata.getMap()
。 - 使用Python语言通过服务端获取Headers实现Server streaming RPC。
在方法内直接调用
context.invocation_metadata()
。
- 使用Java语言通过服务端获取Headers实现Server streaming RPC。
- Client streaming RPC
- 使用Java语言通过服务端获取Headers实现Client streaming RPC。
对Headers无感知。
- 使用Go语言通过服务端获取Headers实现Client streaming RPC。
在方法中直接调用
metadata.FromIncomingContext(ctx)
,上下文参数ctx
从TalkMoreAnswerOne的入参stream
中获取stream.Context()
。 - 使用NodeJS语言通过服务端获取Headers实现Client streaming RPC。
在方法内直接调用
call.metadata.getMap()
。 - 使用Python语言通过服务端获取Headers实现Client streaming RPC。
在方法内直接调用
context.invocation_metadata()
。
- 使用Java语言通过服务端获取Headers实现Client streaming RPC。
- Bidirectional streaming RPC
- 使用Java语言通过服务端获取Headers实现Bidirectional streaming RPC。
对Headers无感知。
- 使用Go语言通过服务端获取Headers实现Bidirectional streaming RPC。
在方法中直接调用
metadata.FromIncomingContext(ctx)
,上下文参数ctx
从TalkBidirectional的入参stream
中获取stream.Context()
。 - 使用NodeJS语言通过服务端获取Headers实现Bidirectional streaming RPC。
在方法内直接调用
call.metadata.getMap()
。 - 使用Python语言通过服务端获取Headers实现Bidirectional streaming RPC。
在方法内直接调用
context.invocation_metadata()
。
- 使用Java语言通过服务端获取Headers实现Bidirectional streaming RPC。
客户端发送Headers
- 基本方法
- 使用Java语言通过客户端发送Headers实现基本方法。
实现拦截器
ClientInterceptor
接口的interceptCall(MethodDescriptor<ReqT, RespT> m
,CallOptions o, Channel c)
方法,实现返回值类型ClientCall<ReqT
,RespT>的start((Listener<RespT> l, Metadata h))
方法,通过h.put(k, v)
填充header信息,put
方法入参k
的类型为Metadata.Key<String>
,v
的类型为String
。 - 使用Go语言通过客户端发送Headers实现基本方法。
metadata.AppendToOutgoingContext(ctx,kv ...) context.Context
- 使用NodeJS语言通过客户端发送Headers实现基本方法。
metadata=call.metadata.getMap()metadata.add(key, headers[key])
- 使用Python语言通过客户端发送Headers实现基本方法。
metadata_dict = {}
变量填充metadata_dict[c.key] = c.value
,最终转为list tuple
类型list(metadata_dict.items())
。
- 使用Java语言通过客户端发送Headers实现基本方法。
- Unary RPC
- 使用Java语言通过客户端发送Headers实现Unary RPC。
对Headers无感知。
- 使用Go语言通过客户端发送Headers实现Unary RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)
。 - 使用NodeJS语言通过客户端发送Headers实现Unary RPC。
在方法内直接使用基本方法。
- 使用Python语言通过客户端发送Headers实现Unary RPC。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送Headers实现Unary RPC。
- Server streaming RPC
- 使用Java语言通过客户端发送Headers实现Server streaming RPC。
对Headers无感知。
- 使用Go语言通过客户端发送Headers实现Server streaming RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)
。 - 使用NodeJS语言通过客户端发送Headers实现Server streaming RPC。
在方法内直接使用基本方法。
- 使用Python语言通过客户端发送Headers实现Server streaming RPC。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送Headers实现Server streaming RPC。
- Client streaming RPC
- 使用Java语言通过客户端发送Headers实现Client streaming RPC。
对Headers无感知。
- 使用Go语言通过客户端发送Headers实现Client streaming RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)
。 - 使用NodeJS语言通过客户端发送Headers实现Client streaming RPC。
在方法内直接使用基本方法。
- 使用Python语言通过客户端发送Headers实现Client streaming RPC。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送Headers实现Client streaming RPC。
- Bidirectional streaming RPC
- 使用Java语言通过客户端发送Headers实现Bidirectional streaming RPC。
对Headers无感知。
- 使用Go语言通过客户端发送Headers实现Bidirectional streaming RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)
。 - 使用NodeJS语言通过客户端发送Headers实现Bidirectional streaming RPC。
在方法内直接使用基本方法。
- 使用Python语言通过客户端发送Headers实现Bidirectional streaming RPC。
在方法内直接使用基本方法。
- 使用Java语言通过客户端发送Headers实现Bidirectional streaming RPC。
Propaganda Headers
由于链路追踪需要将上游传递过来的链路元数据透传给下游,以形成同一条请求链路的完整信息,需要将服务端获取的Headers信息中,和链路追踪相关的Headers透传给向下游发起请求的客户端。
除了Java语言的实现,其他语言的通信模型方法都对Headers有感知,因此可以将服务端读取Headers-传递Headers-客户端发送Headers这三个动作有顺序地在4种通信模型方法内部实现。
Java语言读取和写入Headers是通过两个拦截器分别实现的,因此Propaganda Headers无法在一个顺序的流程里实现,且考虑到并发因素,以及只有读取拦截器知道链路追踪的唯一ID,所以无法通过最直觉的缓存方式搭建两个拦截器的桥梁。

在服务器拦截器读取阶段,通过ctx.withValue(key, metadata)
将Metadata/Header
存入Context,其中Key是Context.Key<String>
类型。然后在客户端拦截器中,通过key.get()
将Metadata从Context
读出,get方法默认使用Context.current()
上下文,这就保证了一次请求的Headers读取和写入使用的是同一个上下文。
有了Propaganda Headers的实现,基于GRPC的链路追踪就有了机制上的保证。
部署和验证网格拓扑
实现gRPC链路追踪之前,您需要部署和验证网格拓扑,确保网格拓扑是可以通信的。
cd go
# 部署
sh apply.sh
# 验证
sh test.sh
如果没有出现异常信息,则说明网格拓扑是可以通信的。
