OpenTelemetry?Java?SDK?高級(jí)用法解析
引言
通過引入 OpenTelemetry SDK,可以觀測(cè)業(yè)務(wù)核心邏輯,比如給核心的業(yè)務(wù)設(shè)置一個(gè) span 以統(tǒng)計(jì)、跟蹤、分析其實(shí)際行為、設(shè)置業(yè)務(wù)屬性指標(biāo)等。此方法具有一定的侵入性。
啟動(dòng)命令
java -javaagent:../opentelemetry-javaagent/opentelemetry-javaagent.jar \ -Dotel.traces.exporter=otlp \ -Dotel.exporter.otlp.endpoint=http://192.168.91.11:4317 \ -Dotel.resource.attributes=service.name=demo,version=dev \ -Dotel.metrics.exporter=otlp \ -jar springboot-opentelemetry-otlp-server.jar --client=true
根據(jù)啟動(dòng)參數(shù) exporter 的不同,sdk 方式也需要引入對(duì)應(yīng)的 exporter ,否則啟動(dòng)失敗。如啟動(dòng)參數(shù)使用了 otlp 和 logging 兩個(gè) exporter,則需要在 pom 添加對(duì)應(yīng)的兩個(gè) exporter 依賴。
如果沒有使用 sdk 相關(guān)依賴,則不需要做對(duì)應(yīng)的調(diào)整。
引入依賴
<dependencies>
...
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-annotations</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>1.21.0-alpha</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.21.0-alpha</version>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.21.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
創(chuàng)建 sdk
/**
* @Description 推薦使用這種方式。<br>
* 這種方式的背后提供的是 noop,也就是默認(rèn)的 provider,會(huì)根據(jù)啟動(dòng)參數(shù)來決定自行構(gòu)造 所需的 provider。
* @return io.opentelemetry.api.OpenTelemetry
**/
@Bean
public OpenTelemetry openTelemetry() {
return AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal(false) // 這里填寫false,否則會(huì)與全局javaagent 沖突
.build()
.getOpenTelemetrySdk();
}
鏈路(Trace)
創(chuàng)建 Tracer
Tracer 主要是用來獲取、創(chuàng)建 span 對(duì)象。注意: Tracer 通常不負(fù)責(zé)進(jìn)行配置,這是TracerProvider 的職責(zé)。 OpenTelemetry interface 提供了默認(rèn)的TracerProvider實(shí)現(xiàn)。
TracerProvider getTracerProvider();
通過openTelemetry() 來獲取 Tracer 對(duì)象
@Bean
public Tracer tracer() {
return openTelemetry().getTracer(appName);
}
創(chuàng)建 Span
除了 Tracer 外,不準(zhǔn)許任何其他 API 創(chuàng)建 Span 。
Span span = tracer.spanBuilder(spanName).startSpan();
獲取當(dāng)前 Span 對(duì)象
獲取當(dāng)前 span 對(duì)象,可以為當(dāng)前 span 設(shè)置 attribute、event 等。
Span span = Span.current();
創(chuàng)建 Attribute
attribute 為 span 的屬性,為當(dāng)前 span 的標(biāo)簽。
span.setAttribute(key, value);
創(chuàng)建 Link Span
一個(gè) Span 可以連接一個(gè)或多個(gè)因果相關(guān)的其他 Span 。鏈接可用于表示批處理操作,其中一個(gè) Span 的初始化由多個(gè) Span 初始化構(gòu)成,其中每個(gè) Span 表示批處理中處理的單個(gè)輸入項(xiàng)。
Span child = tracer.spanBuilder(spanName) .addLink(span(1)) .addLink(span(2)) .addLink(span(3)) .startSpan();
創(chuàng)建 Event
Span 可以攜帶零個(gè)或多個(gè) Span 屬性的命名事件進(jìn)行注釋,每一個(gè)事件都是一個(gè) key:value 鍵值對(duì)并自動(dòng)攜帶相應(yīng)的時(shí)間戳。
span.addEvent(eventName); span.addEvent(eventName,Attributes);
recordException 是 addEvent 的特殊變體,用于記錄異常事件。
創(chuàng)建一個(gè)嵌套的 Span
通過 setParent(parentSpan) 設(shè)置 parentSpan
void parent() {
Span parentSpan = tracer.spanBuilder("parent")
.startSpan();
childSpan(parentSpan);
parentSpan.end();
}
void childSpan(Span parentSpan) {
Span childSpan = tracer.spanBuilder("childSpan")
.setParent(parentSpan)
.startSpan();
// do stuff
childSpan.end();
}
Baggage 用法
Baggage 可以在整個(gè)鏈路傳播,適用于全局埋點(diǎn),比如用戶id埋點(diǎn)、用戶名埋點(diǎn),以便追蹤業(yè)務(wù)數(shù)據(jù)。
gateway方法 set Baggage
// Baggage 用法,此處set
Baggage.current().toBuilder().put("app.username", "gateway").build().makeCurrent();
logger.info("gateway set baggage[app.username] value: gateway");
resource 方法 get Baggage
// Baggage 用法,此get
String baggage = Baggage.current().getEntryValue("app.username");
logger.info("resource get baggage[app.username] value: {}", baggage);
通過已知的traceId和spanId,來構(gòu)造一個(gè)新span。
Tracer 構(gòu)造 span 提供了setParent(context)方法,便于為自定義的 span 構(gòu)造一個(gè)父 span。
tracer.spanBuilder(spanName).setParent(context)
其中 setParent 需要傳入 Context 參數(shù),所以需要構(gòu)造一個(gè)上下文。
而 OpenTelemetry sdk 只提供了一個(gè) create 方法用于創(chuàng)建 SpanContext,其中可以自定義 traceId 和 spanId。
SpanContext create(String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState)
SpanContext 作為表示 Span 的一部分,他必須可以進(jìn)行序列化,并沿著分布式上下文進(jìn)行傳播。SpanContext 是不可變的。
OpenTelemetry SpanContext 符合 W3C TraceContext 規(guī)范。這包含兩個(gè)標(biāo)識(shí)符 - TraceId 和 SpanId - 一套通用 TraceFlags 和 系統(tǒng)特定 TraceState。
TraceId 一個(gè)有效的 TraceId 是一個(gè) 16 字節(jié)的數(shù)組,且至少有一個(gè)非零字節(jié)。
SpanId 一個(gè)有效的 SpanId 是一個(gè) 8 字節(jié)的數(shù)組,且至少有一個(gè)非零字節(jié)。
TraceFlags 包含該 trace 的詳情。不像 TraceFlags,TraceFlags 影響所有的 traces。當(dāng)前版本和定義的 Flags 只有 sampled 。
TraceState 攜帶特定 trace 標(biāo)識(shí)數(shù)據(jù),通過一個(gè) KV 對(duì)數(shù)組進(jìn)行標(biāo)識(shí)。TraceState允許多個(gè)跟蹤系統(tǒng)參與同一個(gè) Trace。完整定義請(qǐng)參考 W3C Trace Context specification 。
本 API 必須實(shí)現(xiàn)創(chuàng)建 SpanContext 的方法。這些方法應(yīng)當(dāng)是唯一的方法用于創(chuàng)建 SpanContext。這個(gè)功能必須在 API 中完全實(shí)現(xiàn),并且不應(yīng)當(dāng)可以被覆蓋。
但 SpanContext 并不是 Context,因而還需要做一層轉(zhuǎn)換。
private Context withSpanContext(SpanContext spanContext, Context context) {
return context.with(Span.wrap(spanContext));
}
完整代碼如下:
/***
* @Description 通過已知的traceId和spanId,來構(gòu)造一個(gè)新span。
* @Param [spanName, traceId, spanId]
* @return java.lang.String
**/
@GetMapping("/customSpanByTraceIdAndSpanId")
@ResponseBody
public String customSpanByTraceIdAndSpanId(String spanName,String traceId,String spanId){
assert StringUtils.isEmpty(spanName):"spanName 不能為空";
assert StringUtils.isEmpty(traceId):"traceId 不能為空";
assert StringUtils.isEmpty(spanId):"spanId 不能為空";
Context context =
withSpanContext(
SpanContext.create(
traceId, spanId, TraceFlags.getSampled(), TraceState.getDefault()),
Context.current());
Span span = tracer.spanBuilder(spanName)
.setParent(context)
.startSpan();
span.setAttribute("attribute.a2", "some value");
span.setAttribute("func","attr");
span.setAttribute("app","otel3");
span.end();
return buildTraceUrl(span.getSpanContext().getTraceId());
}
private Context withSpanContext(SpanContext spanContext, Context context) {
return context.with(Span.wrap(spanContext));
}
需要特別注意,依據(jù)當(dāng)前測(cè)試寫法的請(qǐng)求自身會(huì)產(chǎn)生一個(gè)新的 trace 信息。新構(gòu)造的 span 是依據(jù)傳入的參數(shù)進(jìn)行構(gòu)造。
我們可以通過訪問鏈接來觀察結(jié)果
http://localhost:8080/customSpanByTraceIdAndSpanId?spanName=tSpan&traceId=24baeeddfbb35fceaf4c18e7cae58fe1&spanId=ff1955b4f0eacc4f
指標(biāo)(Metrics)
OpenTelemetry 還提供了 metric 相關(guān)操作的 API。
span 提供關(guān)于應(yīng)用程序的詳細(xì)信息,但生成的數(shù)據(jù)與系統(tǒng)上的負(fù)載成正比。相比之下,度量將單個(gè)度量組合成聚合,并生成作為系統(tǒng)負(fù)載函數(shù)的常量數(shù)據(jù)。聚合缺乏診斷低級(jí)問題所需的細(xì)節(jié),但是通過幫助識(shí)別趨勢(shì)和提供應(yīng)用程序運(yùn)行時(shí)遙測(cè)來補(bǔ)充跨度。
度量 API 定義了各種工具。儀器記錄測(cè)量值,這些測(cè)量值由度量 SDK 聚合,并最終導(dǎo)出到進(jìn)程外。儀器有同步和異步兩種。同步儀器記錄測(cè)量結(jié)果。異步儀器注冊(cè)回調(diào),每次采集調(diào)用一次,并記錄該時(shí)間點(diǎn)的測(cè)量值。下列儀器可供選擇:
LongCounter/DoubleCounter: 只記錄正數(shù)值,有同步和異步選項(xiàng)。用于計(jì)算事物,例如通過網(wǎng)絡(luò)發(fā)送的字節(jié)數(shù)。默認(rèn)情況下,計(jì)數(shù)器測(cè)量被聚合為始終遞增的單調(diào)和。
LongUpDownCounter/DoubleUpDownCounter: 記錄正負(fù)值,有同步和異步選項(xiàng)。對(duì)于計(jì)算上升和下降的東西很有用,比如隊(duì)列的大小。默認(rèn)情況下,向上向下計(jì)數(shù)器測(cè)量被聚合為非單調(diào)和。
LongGauge/DoubleGauge: 用異步回調(diào)測(cè)量瞬時(shí)值。用于記錄不能跨屬性合并的值,如 CPU 使用率百分比。默認(rèn)情況下,量規(guī)測(cè)量被聚合為量規(guī)。
LongHistogram/DoubleHistogram(長(zhǎng)直方圖/雙直方圖): 記錄對(duì)直方圖分布分析最有用的測(cè)量值。沒有異步選項(xiàng)可用。用于記錄 HTTP 服務(wù)器處理請(qǐng)求所花費(fèi)的時(shí)間等。默認(rèn)情況下,直方圖測(cè)量被聚合為顯式的桶直方圖。
獲取 Meter 對(duì)象
API定義了一個(gè) Meter 接口。該接口由一組 instrument 構(gòu)造器,和一個(gè)使用原子方式批量獲取測(cè)量值的工具組成。Meter 可以通過 MeterProvider 的 getMeter(name)方法來創(chuàng)建一個(gè)新實(shí)例。 MeterProvider 通常被期望作為單例來使用。 其實(shí)現(xiàn)應(yīng)作為 MeterProvider 全局的唯一實(shí)現(xiàn)。通過 Meter 對(duì)象可以構(gòu)建不同類型的 metric。
@Bean
public Meter meter() {
return openTelemetry().getMeter(appName);
}
這里跳過了 MeterProvider 的細(xì)節(jié),主要原因在于 OpenTelemetry Interface 提供了 MeterProvider 的默認(rèn) noop 實(shí)現(xiàn)。
default MeterProvider getMeterProvider() {
return MeterProvider.noop();
}
構(gòu)建 gauge 類型的 metric
meter().gaugeBuilder("connections")
.setDescription("當(dāng)前Socket.io連接數(shù)")
.setUnit("1")
.buildWithCallback(
result -> {
System.out.println("metrics");
for (int i = 1; i < 4; i++) {
result.record(
i,
Attributes.of(
AttributeKey.stringKey("id"),
"a" + i));
}
});
buildWithCallback 是一個(gè)回調(diào)函數(shù),是支持異步 API 并按需收集度量數(shù)據(jù)的附加工具,按間隔收集數(shù)據(jù),默認(rèn) 1min 一次。
參考
springboot-opentelemetry-otlp-server
以上就是OpenTelemetry Java SDK 高級(jí)用法解析的詳細(xì)內(nèi)容,更多關(guān)于OpenTelemetry Java SDK的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Springboot @Cacheable 注解(指定緩存位置)
這篇文章主要介紹了詳解Springboot @Cacheable 注解(指定緩存位置),使用? @Cacheable ?注解就可以將運(yùn)行結(jié)果緩存,以后查詢相同的數(shù)據(jù),直接從緩存中取,不需要調(diào)用方法,需要的朋友可以參考下2023-09-09
Java簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)代理模式過程解析
這篇文章主要介紹了Java動(dòng)態(tài)代理模式簡(jiǎn)單案例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Java Spring-IOC容器與Bean管理之基于注解的方式案例詳解
這篇文章主要介紹了Java Spring-IOC容器與Bean管理之基于注解的方式案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
解決JSTL foEach標(biāo)簽 刷新報(bào)錯(cuò)的方法
本篇文章是對(duì)JSTL foEach標(biāo)簽刷新報(bào)錯(cuò)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
itext生成PDF設(shè)置頁眉頁腳的實(shí)例詳解
Java實(shí)現(xiàn)非對(duì)稱加密的三種方法

