可觀測鏈路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名稱下的鏈路,查看調用鏈路。