通過引入 OpenTelemetry SDK,可以觀測業務核心邏輯,比如給核心的業務設定一個 span 以統計、跟蹤、分析其實際行為、設定業務屬性名額等。此方法具有一定的侵入性。
啟動指令
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
複制代碼
根據啟動參數 exporter 的不同,sdk 方式也需要引入對應的 exporter ,否則啟動失敗。如啟動參數使用了 otlp 和 logging 兩個 exporter,則需要在 pom 添加對應的兩個 exporter 依賴。
如果沒有使用 sdk 相關依賴,則不需要做對應的調整。
引入依賴
<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>
複制代碼
建立 sdk
@Bean
public OpenTelemetry openTelemetry() {
return AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal(false)
.build()
.getOpenTelemetrySdk();
}
複制代碼
鍊路(Trace)
建立 Tracer
Tracer 主要是用來擷取、建立 span 對象。注意: Tracer 通常不負責進行配置,這是TracerProvider 的職責。 OpenTelemetry interface 提供了預設的TracerProvider實作。
TracerProvider getTracerProvider();
複制代碼
通過openTelemetry() 來擷取 Tracer 對象
@Bean
public Tracer tracer() {
return openTelemetry().getTracer(appName);
}
複制代碼
建立 Span
除了 Tracer 外,不準許任何其他 API 建立 Span 。
Span span = tracer.spanBuilder(spanName).startSpan();
複制代碼
擷取目前 Span 對象
擷取目前 span 對象,可以為目前 span 設定 attribute、event 等。
Span span = Span.current();
複制代碼
建立 Attribute
attribute 為 span 的屬性,為目前 span 的标簽。
span.setAttribute(key, value);
複制代碼
建立 Link Span
一個 Span 可以連接配接一個或多個因果相關的其他 Span 。連結可用于表示批處理操作,其中一個 Span 的初始化由多個 Span 初始化構成,其中每個 Span 表示批進行中處理的單個輸入項。
Span child = tracer.spanBuilder(spanName)
.addLink(span(1))
.addLink(span(2))
.addLink(span(3))
.startSpan();
複制代碼
建立 Event
Span 可以攜帶零個或多個 Span 屬性的命名事件進行注釋,每一個事件都是一個 key:value 鍵值對并自動攜帶相應的時間戳。
span.addEvent(eventName);
span.addEvent(eventName,Attributes);
複制代碼
recordException 是 addEvent 的特殊變體,用于記錄異常事件。
建立一個嵌套的 Span
通過 setParent(parentSpan) 設定 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 可以在整個鍊路傳播,适用于全局埋點,比如使用者id埋點、使用者名埋點,以便追蹤業務資料。
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,來構造一個新span。
Tracer 構造 span 提供了setParent(context)方法,便于為自定義的 span 構造一個父 span。
tracer.spanBuilder(spanName).setParent(context)
複制代碼
其中 setParent 需要傳入 Context 參數,是以需要構造一個上下文。
而 OpenTelemetry sdk 隻提供了一個 create 方法用于建立 SpanContext,其中可以自定義 traceId 和 spanId。
SpanContext create(String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState)
複制代碼
SpanContext 作為表示 Span 的一部分,他必須可以進行序列化,并沿着分布式上下文進行傳播。SpanContext 是不可變的。
OpenTelemetry SpanContext 符合 W3C TraceContext 規範。這包含兩個辨別符 - TraceId 和 SpanId - 一套通用 TraceFlags 和 系統特定 TraceState。
- TraceId 一個有效的 TraceId 是一個 16 位元組的數組,且至少有一個非零位元組。
- SpanId 一個有效的 SpanId 是一個 8 位元組的數組,且至少有一個非零位元組。
- TraceFlags 包含該 trace 的詳情。不像 TraceFlags,TraceFlags 影響所有的 traces。目前版本和定義的 Flags 隻有 sampled 。
- TraceState 攜帶特定 trace 辨別資料,通過一個 KV 對數組進行辨別。TraceState允許多個跟蹤系統參與同一個 Trace。完整定義請參考 W3C Trace Context specification 。
本 API 必須實作建立 SpanContext 的方法。這些方法應當是唯一的方法用于建立 SpanContext。這個功能必須在 API 中完全實作,并且不應當可以被覆寫。
但 SpanContext 并不是 Context,因而還需要做一層轉換。
private Context withSpanContext(SpanContext spanContext, Context context) {
return context.with(Span.wrap(spanContext));
}
複制代碼
完整代碼如下:
/***
* @Description 通過已知的traceId和spanId,來構造一個新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));
}
複制代碼
需要特别注意,依據目前測試寫法的請求自身會産生一個新的 trace 資訊。新構造的 span 是依據傳入的參數進行構造。
我們可以通過通路連結來觀察結果
http://localhost:8080/customSpanByTraceIdAndSpanId?spanName=tSpan&traceId=24baeeddfbb35fceaf4c18e7cae58fe1&spanId=ff1955b4f0eacc4f
名額(Metrics)
OpenTelemetry 還提供了 metric 相關操作的 API。
span 提供關于應用程式的詳細資訊,但生成的資料與系統上的負載成正比。相比之下,度量将單個度量組合成聚合,并生成作為系統負載函數的常量資料。聚合缺乏診斷低級問題所需的細節,但是通過幫助識别趨勢和提供應用程式運作時遙測來補充跨度。
度量 API 定義了各種工具。儀器記錄測量值,這些測量值由度量 SDK 聚合,并最終導出到程序外。儀器有同步和異步兩種。同步儀器記錄測量結果。異步儀器注冊回調,每次采集調用一次,并記錄該時間點的測量值。下列儀器可供選擇:
- LongCounter/DoubleCounter: 隻記錄正數值,有同步和異步選項。用于計算事物,例如通過網絡發送的位元組數。預設情況下,計數器測量被聚合為始終遞增的單調和。
- LongUpDownCounter/DoubleUpDownCounter: 記錄正負值,有同步和異步選項。對于計算上升和下降的東西很有用,比如隊列的大小。預設情況下,向上向下計數器測量被聚合為非單調和。
- LongGauge/DoubleGauge: 用異步回調測量瞬時值。用于記錄不能跨屬性合并的值,如 CPU 使用率百分比。預設情況下,量規測量被聚合為量規。
- LongHistogram/DoubleHistogram(長直方圖/雙直方圖): 記錄對直方圖分布分析最有用的測量值。沒有異步選項可用。用于記錄 HTTP 伺服器處理請求所花費的時間等。預設情況下,直方圖測量被聚合為顯式的桶直方圖。
擷取 Meter 對象
API定義了一個 Meter 接口。該接口由一組 instrument 構造器,和一個使用原子方式批量擷取測量值的工具組成。Meter 可以通過 MeterProvider 的 getMeter(name)方法來建立一個新執行個體。 MeterProvider 通常被期望作為單例來使用。 其實作應作為 MeterProvider 全局的唯一實作。通過 Meter 對象可以建構不同類型的 metric。
@Bean
public Meter meter() {
return openTelemetry().getMeter(appName);
}
複制代碼
這裡跳過了 MeterProvider 的細節,主要原因在于 OpenTelemetry Interface 提供了 MeterProvider 的預設 noop 實作。
default MeterProvider getMeterProvider() {
return MeterProvider.noop();
}
複制代碼
建構 gauge 類型的 metric
meter().gaugeBuilder("connections")
.setDescription("目前Socket.io連接配接數")
.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 是一個回調函數,是支援異步 API 并按需收集度量資料的附加工具,按間隔收集資料,預設 1min 一次。
原文連結:https://juejin.cn/post/7199833312579321912