可觀測鏈路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如果沒有出現異常資訊,則說明網格拓撲可以正常通訊。
部署後的Service Mesh拓撲如下圖所示。
鏈路追蹤
將鏈路追蹤資料擷取到阿里雲可觀測鏈路OpenTelemetry版。具體操作,請參見將鏈路追蹤資料擷取到阿里雲可觀測鏈路OpenTelemetry版。
登入可觀測鏈路OpenTelemetry版,在左側導覽列,單擊鏈路入口。
在鏈路入口頁面,單擊目標應用的應用拓撲。
可以看到完整的鏈路,包括本地請求端-Ingressgateway-grpc-server-svc1-grpc-server-svc2-grpc-server-svc3。

在全鏈路彙總頁面,單擊全鏈路彙總頁簽,查看全鏈路彙總。

在全鏈路彙總頁簽,單擊Span名稱下的鏈路,查看調用鏈路。
