全部產品
Search
文件中心

Alibaba Cloud Service Mesh:通過ASM實現gRPC鏈路追蹤

更新時間:Jun 30, 2024

可觀測鏈路OpenTelemetry版為分布式應用的開發人員提供了完整的調用鏈路還原、調用請求量統計、鏈路拓撲、應用依賴分析等工具。本文介紹如何通過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]: MetadataValueMetadataValue類型定義為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拓撲如下圖所示。網路拓撲

鏈路追蹤

  1. 將鏈路追蹤資料擷取到阿里雲可觀測鏈路OpenTelemetry版。具體操作,請參見將鏈路追蹤資料擷取到阿里雲可觀測鏈路OpenTelemetry版

  2. 登入可觀測鏈路OpenTelemetry版,在左側導覽列,單擊鏈路入口

  3. 鏈路入口頁面,單擊目標應用的應用拓撲

    可以看到完整的鏈路,包括本地請求端-Ingressgateway-grpc-server-svc1-grpc-server-svc2-grpc-server-svc3。鏈路追蹤

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

    全鏈路彙總

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

    調用鏈路