天天看點

實戰OpenTelemetry Trace:使用Java實作分布式跟蹤

OpenTelemetry是一款開源的分布式跟蹤系統,它提供了一套标準的API和SDK,可以幫助我們收集、存儲和分析分布式系統的跟蹤資料。本文将介紹如何使用OpenTelemetry Trace來實作分布式系統的跟蹤,并通過示例代碼來展示OpenTelemetry Trace的使用方式。

1. 什麼是OpenTelemetry Trace?

OpenTelemetry Trace是一種分布式跟蹤技術,可以幫助開發者在複雜的應用程式中識别和解決問題。它允許開發者在跨越多個應用程式和服務的請求路徑上跟蹤請求,并提供有關這些請求的有用資訊。

OpenTelemetry Trace通過在請求路徑上建立一系列跨度(Spans)來實作跟蹤。每個跨度代表了請求路徑上的一部分,可以包含一些有用的資訊,例如跨度的起始時間和結束時間、跨度的标簽等等。這些資訊可以幫助開發者識别潛在的性能問題和故障。

2. OpenTelemetry Trace的核心元件

在使用OpenTelemetry Trace時,我們需要了解其核心元件。這些元件包括跨度(Span)、跨度上下文(Span Context)、跨度處理器(Span Processor)和跨度導出器(Span Exporter)。

跨度(Span)

跨度是OpenTelemetry Trace的核心概念之一,它代表了請求路徑上的一部分。每個跨度包含一些中繼資料和事件,例如跨度的名稱、起始時間和結束時間等等。跨度可以嵌套,進而形成一個跨度樹。

跨度上下文(Span Context)

跨度上下文是跨度的中繼資料,包含了跨度的辨別符和其他一些資訊。跨度上下文在跨度之間傳遞,以便在分布式環境中對請求路徑進行跟蹤。

跨度處理器(Span Processor)

跨度處理器用于在本地處理跨度。它可以對跨度進行修改、過濾或記錄等操作。

跨度導出器(Span Exporter)

跨度導出器用于将跨度資料發送到外部系統,例如日志記錄系統、監控系統等等。

3. 使用OpenTelemetry Trace實作分布式跟蹤

下面将介紹如何在Java應用程式中使用OpenTelemetry Trace實作分布式跟蹤。具體步驟如下:

步驟1:添加OpenTelemetry依賴項

首先,需要添加OpenTelemetry Trace的依賴項。以下是使用Maven添加OpenTelemetry Trace依賴項的示例代碼:

xml複制代碼<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    <version>1.25.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp-trace</artifactId>
    <version>1.25.0</version>
</dependency>

           

在這個例子中,我們添加了opentelemetry-api和opentelemetry-exporter-otlp-trace依賴項。前者是OpenTelemetry Trace的核心庫,後者是将跟蹤資料導出到OTLP收集器的庫。

步驟2:建立TracerProvider

接下來,需要建立一個TracerProvider執行個體。TracerProvider用于建立Tracer執行個體,并将其注冊到全局的OpenTelemetry Trace執行個體中。以下是建立TracerProvider的示例代碼:

java複制代碼TracerProvider tracerProvider = OpenTelemetrySdk.getTracerProvider();
           

在這個例子中,我們擷取了預設的TracerProvider執行個體。

步驟3:建立Span

一旦建立了TracerProvider,就可以使用它建立Span。以下是建立Span的示例代碼:

java複制代碼import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.api.GlobalOpenTelemetry;

TracerProvider tracerProvider = GlobalOpenTelemetry.getTracerProvider();
Tracer tracer = tracerProvider.get("example-tracer");

Span span = tracer.spanBuilder("example-span").startSpan();
try {
  // Span code here
} finally {
  span.end();
}

           

在這個示例中,我們首先使用全局的TracerProvider和Tracer執行個體化一個Span。然後,我們使用SpanBuilder來開始Span,并在執行結束後結束Span。在這裡,我們使用了try-finally塊來確定Span的結束。

步驟4:建立Child Span

有時候,我們需要建立一個新的Span,并将其連結到目前的Span。例如,在調用下遊服務時,可以建立一個新的Span,并将其連結到目前Span。以下是建立和連結Span的示例代碼:

java複制代碼import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.api.GlobalOpenTelemetry;

TracerProvider tracerProvider = GlobalOpenTelemetry.getTracerProvider();
Tracer tracer = tracerProvider.get("example-tracer");

Span parentSpan = tracer.spanBuilder("parent-span").startSpan();
try {
  // Create child span
  Span childSpan = tracer.spanBuilder("child-span")
      .setParent(parentSpan)
      .startSpan();
  try {
    // Child span code here
  } finally {
    childSpan.end();
  }
} finally {
  parentSpan.end();
}
           

在這個示例中,我們首先建立了一個Parent Span,然後在它的作用域内,使用SpanBuilder建立一個Child Span,并将Parent Span設定為Child Span的父Span。我們在Child Span執行結束後結束它,然後在Parent Span執行結束後結束它。

步驟5:自定義Span的屬性、事件和上下文

在OpenTelemetry Trace中,Span可以攜帶各種屬性和事件,可以通過Span#setAttribute和Span#addEvent方法進行設定。例如,可以将HTTP請求的URL作為Span的屬性:

java複制代碼Span span = tracer.spanBuilder("http-request").startSpan();
span.setAttribute("http.url", "http://example.com");
           

除了設定屬性和事件,還可以通過Span#setNoParent和Span#setParent方法設定Span的父子關系,通過Span#setSpanKind方法設定Span的類型(例如SpanKind.SERVER表示服務端Span,SpanKind.CLIENT表示用戶端Span),以及通過Span#setSpanStatus方法設定Span的狀态(例如Status.OK表示正常,Status.ERROR表示異常)。

除了使用Span的API,還可以使用OpenTelemetry的上下文API來在Span之間傳遞資料。上下文可以存儲一些Span之間共享的資訊,例如請求ID、使用者ID等。OpenTelemetry提供了Context和Baggage兩種上下文,其中Context是一種輕量級的上下文,Baggage則是一種可以攜帶更多資料的上下文。可以使用Context#current擷取目前線程的上下文,并通過Context#with方法建立一個新的帶有指定鍵值對的上下文。

以下是一個示例,展示如何将請求ID存儲在上下文中并在多個Span之間共享:

java複制代碼// 建立一個新的Span,設定請求ID屬性
Span span = tracer.spanBuilder("http-request").startSpan();
String requestId = generateRequestId();
span.setAttribute("request.id", requestId);

// 将請求ID存儲在上下文中
Context contextWithRequestId = Context.current().with(SpanContextKey.KEY, requestId);

// 在另一個Span中擷取請求ID屬性
Span anotherSpan = tracer.spanBuilder("another-span").startSpan();
String requestIdFromContext = Baggage.current().getEntry(SpanContextKey.KEY).getValue();
anotherSpan.setAttribute("request.id", requestIdFromContext);

           

注意,要使用Baggage來擷取上下文中的鍵值對,需要先通過Baggage#builder方法建立一個BaggageBuilder對象,并在其中使用BaggageBuilder#put方法設定鍵值對,然後通過BaggageBuilder#build方法建立一個Baggage對象。可以将Baggage對象存儲在Context中,也可以将其作為Span的屬性存儲。

步驟6:導出跟蹤資料

最後,需要将跟蹤資料導出到後端收集器中。以下是将跟蹤資料導出到OTLP收集器的示例代碼:

java複制代碼OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder()
    .setEndpoint("otelcol:55680")
    .build();
SpanExporterSdk spanExporter = SpanExporterSdk.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)).build();
tracerProvider = SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(exporter)).build();

           

在這個例子中,我們建立了一個OtlpGrpcSpanExporter執行個體,并将其配置為将跟蹤資料導出到指定的OTLP收集器。然後,我們使用SpanExporterSdk和TracerProviderSdk建立了一個SpanExporter執行個體和TracerProvider執行個體,并将其與OtlpGrpcSpanExporter執行個體綁定。這樣,當Span結束時,SpanExporter就會自動将跟蹤資料導出到OTLP收集器。

4. 常見的問題和解決方案

在實作 OpenTelemetry Trace 的過程中,還有一些注意點需要我們關注,以下是一些常見的問題和解決方案:

4.1 如何在代碼中設定采樣率?

在 OpenTelemetry 中,采樣率可以通過 Sampler 來進行設定。可以選擇使用常見的幾種 Sampler,比如 AlwaysOnSampler、AlwaysOffSampler、ProbabilitySampler 等。

java複制代碼// 設定采樣率為 0.5,也就是采樣 50% 的請求
Sampler sampler = ProbabilitySampler.create(0.5);

// 初始化 TracerProvider,并将 Sampler 設定為采樣器
TracerProvider tracerProvider = OpenTelemetrySdk.getTracerProviderBuilder()
        .setSampler(sampler)
        .build();

           

4.2 如何自定義 Span?

OpenTelemetry Trace 中的 Span 是通過 Tracer 來建立和管理的。我們可以通過設定 Span 的各種屬性來記錄更多的資訊,比如設定 Span 的名稱、記錄事件等等。下面是一個建立自定義 Span 的示例:

java複制代碼// 擷取 Tracer
Tracer tracer = OpenTelemetry.getGlobalTracerProvider().get("myapp", "1.0.0");

// 建立一個自定義 Span
Span span = tracer.spanBuilder("my span")
        .setAttribute("attribute1", "value1")
        .startSpan();
        
// 記錄事件
span.addEvent("event1");

// 結束 Span
span.end();

           

4.3 如何處理異步調用?

在處理異步調用時,可能會出現跨線程的情況。為了能夠在異步任務中正确地記錄 Trace,需要使用 Context 來傳遞 Span。

java複制代碼// 擷取目前的 Span
Span span = Span.current();

// 将 Span 注入到 Context 中
Context context = Context.current().with(Span.wrap(span));

// 異步任務中擷取 Span
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 從 Context 中擷取 Span
    Span span = Span.fromContext(context);

    // 在異步任務中記錄事件
    span.addEvent("event in async task");
});

           

4.4 如何在日志中記錄 Trace?

在實際的應用中,我們通常會将一些關鍵資訊記錄在日志中,友善查找問題。在 OpenTelemetry 中,可以通過設定 MDC 來将目前的 TraceId 和 SpanId 記錄在日志中。

java複制代碼// 将 TraceId 和 SpanId 記錄在 MDC 中
MDC.put("traceId", Span.current().getSpanContext().getTraceIdAsHexString());
MDC.put("spanId", Span.current().getSpanContext().getSpanIdAsHexString());

// 記錄日志
log.info("this is a log message");

// 清除 MDC 中的資訊
MDC.clear();

           

以上是在實戰中常見的一些問題和解決方案,希望對大家有所幫助。在使用 OpenTelemetry Trace 的過程中,如果遇到問題,可以參考官方文檔或者社群中的資料,或者提問社群中的成員。

5.OpenTelemetry Trace在遠端傳遞跟蹤資訊

以下是一個OpenTelemetry Trace在遠端傳遞跟蹤資訊的Java代碼示例:

java複制代碼//建立Tracer執行個體
Tracer tracer = OpenTelemetry.getTracerProvider().get("example");

//建立一個Span
Span span = tracer.spanBuilder("my span").startSpan();

try (Scope scope = span.makeCurrent()) {
  //在Span中執行業務代碼
  doSomeWork();
  
  //建立一個子Span
  Span childSpan = tracer.spanBuilder("my child span").startSpan();

  try (Scope childScope = childSpan.makeCurrent()) {
    //在子Span中執行業務代碼
    doSomeMoreWork();
  } finally {
    //結束子Span
    childSpan.end();
  }
} finally {
  //結束Span
  span.end();
}

//建立一個新的Context,并将目前Span添加到其中
Context context = Context.current().with(span);

//将Context序列化并發送到遠端服務
sendContextToRemoteService(serializeContext(context));

           

在上述代碼中,我們首先建立了一個Tracer執行個體和一個Span,并在Span中執行了業務代碼。然後,我們建立了一個子Span,并在其中執行了更多的業務代碼。最後,我們将目前的Span添加到一個新的Context對象中,并将Context序列化後發送到遠端服務。

需要注意的是,在實際使用中,我們需要根據具體的場景和需求,使用适當的方式将Context傳遞到遠端服務中。例如,可以使用HTTP請求、消息隊列、分布式鎖等方式進行傳遞。

另外,需要注意在傳遞跟蹤資訊時,需要確定傳遞的資料量不會對網絡性能和業務性能造成負面影響。可以通過壓縮、采樣等方式進行優化和控制。

6. 總結

在本文中,我們介紹了如何使用OpenTelemetry Trace在Java應用程式中實作分布式跟蹤。我們了解了OpenTelemetry Trace的核心概念,包括Span、Trace、Tracer和Exporter,并示範了如何使用這些概念來建立和連結Span,并将跟蹤資料導出到後端收集器中。希望這篇文章能夠幫助讀者更好地了解OpenTelemetry Trace,并在實際項目中應用它。

原文連結:https://juejin.cn/post/7233040863257034813

繼續閱讀