可观测链路OpenTelemetry版为分布式应用的开发者提供了完整的调用链路还原、调用请求量统计、链路拓扑、应用依赖分析等工具。本文介绍如何通过Headers在ASM实现gRPC链路追踪。
前提条件
已创建ASM实例。具体操作,请参见创建ASM实例。
阿里云账号已开通可观测链路OpenTelemetry版。关于如何计费,请参见计费规则。
示例工程
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获取键值对。
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()。
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()。
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()。
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()。
客户端发送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())。
Unary RPC
使用Java语言通过客户端发送Headers实现Unary RPC。
对Headers无感知。
使用Go语言通过客户端发送Headers实现Unary RPC。
在方法中直接调用
metadata.AppendToOutgoingContext(ctx,kv)。使用NodeJS语言通过客户端发送Headers实现Unary RPC。
在方法内直接使用基本方法。
使用Python语言通过客户端发送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。
在方法内直接使用基本方法。
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。
在方法内直接使用基本方法。
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。
在方法内直接使用基本方法。
propagate Headers
由于链路追踪需要将上游传递过来的链路元数据透传给下游,以形成同一条请求链路的完整信息,需要将服务端获取的Headers信息中,和链路追踪相关的Headers透传给向下游发起请求的客户端。
除了Java语言的实现,其他语言的通信模型方法都对Headers有感知,因此可以将服务端读取Headers-传递Headers-客户端发送Headers这三个动作有顺序地在4种通信模型方法内部实现。
Java语言读取和写入Headers是通过两个拦截器分别实现的,因此propagate Headers无法在一个顺序的流程里实现,且考虑到并发因素,以及只有读取拦截器知道链路追踪的唯一ID,所以无法通过最直觉的缓存方式搭建两个拦截器的桥梁。
Java语言的实现提供了一种Metadata-Context Propagation的机制。
在服务器拦截器读取阶段,通过ctx.withValue(key, metadata)将Metadata/Header存入Context,其中Key是Context.Key<String>类型。然后在客户端拦截器中,通过key.get()将Metadata从Context读出,get方法默认使用Context.current()上下文,这就保证了一次请求的Headers读取和写入使用的是同一个上下文。
有了propagate Headers的实现,基于GRPC的链路追踪就有了机制上的保证。
部署和验证网格拓扑
实现gRPC链路追踪之前,您需要部署和验证网格拓扑,确保网格拓扑是可以通信的。
进入示例工程的tracing目录,该目录下包含4种编程语言的部署脚本。以下以Go版本为例,部署和验证网格拓扑。
cd go
# 部署
sh apply.sh
# 验证
sh test.sh如果没有出现异常信息,则说明网格拓扑可以正常通信。
部署后的服务网格拓扑如下图所示。
链路追踪
将链路追踪数据采集到阿里云可观测链路OpenTelemetry版。具体操作,请参见配置上报ASM链路追踪数据。
登录可观测链路OpenTelemetry版,在左侧导航栏,单击链路入口。
在链路入口页面,单击目标应用的应用拓扑。
可以看到完整的链路,包括本地请求端-Ingressgateway-grpc-server-svc1-grpc-server-svc2-grpc-server-svc3。

在全链路聚合页面,单击全链路聚合页签,查看全链路聚合。

在全链路聚合页签,单击Span名称下的链路,查看调用链路。
