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