可观测链路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版。具体操作,请参见将链路追踪数据采集到阿里云可观测链路OpenTelemetry版。
登录可观测链路OpenTelemetry版,在左侧导航栏,单击链路入口。
在链路入口页面,单击目标应用的应用拓扑。
可以看到完整的链路,包括本地请求端-Ingressgateway-grpc-server-svc1-grpc-server-svc2-grpc-server-svc3。
在全链路聚合页面,单击全链路聚合页签,查看全链路聚合。
在全链路聚合页签,单击Span名称下的链路,查看调用链路。