OpenTelemetry初識(shí)及調(diào)用鏈Trace詳解
前言
OpenTelemetry作為一個(gè)分布式追蹤的項(xiàng)目,他支持非常多的語(yǔ)言,如Java,Golang,Python等,鑒于筆者的主力語(yǔ)言為Java,并且后續(xù)需要介紹OpenTelemetry的Java Agent實(shí)現(xiàn),所以后續(xù)文章中的相關(guān)知識(shí)點(diǎn)都以Java或者Java Sdk的方式為主。
初識(shí)OpenTelemetry
在微服務(wù)廣泛發(fā)展和使用的當(dāng)下,對(duì)于整個(gè)微服務(wù)體系的使用情況的觀察以及服務(wù)依賴(lài)調(diào)用情況都不再像以往那么清晰明了。而這正是OpenTelemetry能夠?yàn)槲覀兲峁┑哪芰Α?/p>
OpenTelemetry源自O(shè)penSencuc和OpenTracing的合并,它的目標(biāo)是集成Trace,Metrics,Logging能力來(lái)提供可觀測(cè)性。過(guò)去的分布式追蹤往往是各做各的,沒(méi)有固定的標(biāo)準(zhǔn),各個(gè)分布式追蹤方案各顯神通,使用不同的協(xié)議,不同的標(biāo)準(zhǔn)。但是OpenTelemetry不同,它提供了一系列的標(biāo)準(zhǔn),并且他的可插拔式的架構(gòu)為將來(lái)的協(xié)議和數(shù)據(jù)結(jié)構(gòu)擴(kuò)展提供了便利的方式。
調(diào)用鏈Trace
分布式調(diào)用鏈,俗稱(chēng)調(diào)用鏈,用來(lái)記錄請(qǐng)求的路徑整體路徑。下圖是一個(gè)典型的的請(qǐng)求以及其RPC調(diào)用的鏈路:

從圖中我們可以很清晰的了解到剛才的請(qǐng)求是怎么一個(gè)流轉(zhuǎn)過(guò)程,經(jīng)過(guò)了什么組件和服務(wù),接口。這就是調(diào)用鏈的作用之一,讓我們的請(qǐng)求鏈路更加透明清晰。
Span
Span在調(diào)用鏈中是一個(gè)基礎(chǔ)的單元,一個(gè)調(diào)用鏈?zhǔn)怯珊芏嗟腟pan組成的。在一個(gè)Span中會(huì)包含如下信息:
- 名稱(chēng)
- 父Span的ID,root節(jié)點(diǎn)的父Span為空
- 開(kāi)始與結(jié)束時(shí)間戳
- Span Context
- Attributes
- Span事件
- Span Links
- Span狀態(tài)
- Span Kind
以下是一個(gè)典型的Span結(jié)構(gòu):
{
"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
"parent_id": "",
"span_id": "086e83747d0e381e",
"name": "/v1/sys/health",
"start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
"end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
"status_code": "STATUS_CODE_OK",
"status_message": "",
"attributes": {
"net.transport": "IP.TCP",
"net.peer.ip": "172.17.0.1",
"net.peer.port": "51820",
"net.host.ip": "10.177.2.152",
"net.host.port": "26040",
"http.method": "GET",
"http.target": "/v1/sys/health",
"http.server_name": "mortar-gateway",
"http.route": "/v1/sys/health",
"http.user_agent": "Consul Health Check",
"http.scheme": "http",
"http.host": "10.177.2.152:26040",
"http.flavor": "1.1"
},
"events": [
{
"name": "",
"message": "OK",
"timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
}
]
}
Span Context
Span Context可以理解為上下文,是Span中包含的不可變的對(duì)象。在Span Context中包含了:
- traceId:調(diào)用鏈ID
- spanId:Span的ID
- traceFlag:二進(jìn)制形式的調(diào)用鏈標(biāo)志位,一般用于表示調(diào)用鏈?zhǔn)欠癫蓸樱╥sSampled)
- traceState:承載調(diào)用鏈信息的K-V結(jié)構(gòu)列表
Attributes
Attributes是一個(gè)用來(lái)攜帶信息的K-V結(jié)構(gòu)。
在java sdk中可以通過(guò):
Span.current().setAttribute("My Attributes", "attr");

來(lái)自定義你想要設(shè)置的Attributes。當(dāng)然在OpenTelemetry中默認(rèn)內(nèi)置的那些Instrumentation都會(huì)有定義一些指定標(biāo)準(zhǔn)化的的Attributes,詳情可以參照Semantic Attributes
Span事件
Span事件(Events)是一種事件機(jī)制,可以將事件觸發(fā)與具體的Span進(jìn)行綁定,然后在調(diào)用鏈頁(yè)面展示出來(lái)。如下事例:
Span.current().addEvent("My Event");

Span Links
Span Links是一種能夠?qū)⒄{(diào)用鏈關(guān)聯(lián)起來(lái)的技術(shù),通過(guò)配置關(guān)聯(lián)的Span,可以在頁(yè)面中展現(xiàn)關(guān)聯(lián)的調(diào)用鏈信息。不過(guò)請(qǐng)注意Span Links必須要在Span創(chuàng)建時(shí)才能添加,不像Events和Attributes一樣能在Span創(chuàng)建之后添加。例子如下:
Tracer tracer = GlobalOpenTelemetry.getTracer("1111");
Span span = tracer.spanBuilder("start")
.addLink(SpanContext.create("ee868088dfd10adbaa459c9aa353b112", "53b11b6c55010604",
TraceFlags.getDefault(), TraceState.getDefault())).startSpan();
span.end();

Span狀態(tài)
Span狀態(tài)(Status)是定義好的Span的狀態(tài),有如下幾種:
- Unset
- Ok
- Error
Span Kind
Span Kind是指Span類(lèi)型,有如下幾種:
- Server
- Client
- Producer
- Consumer
- Internal
顧名思義Server/Client指的是服務(wù)端/客戶(hù)端,Producer/Consumer指的是生產(chǎn)者/消費(fèi)者,顯然這個(gè)一般適用于消息隊(duì)列,Internal是內(nèi)部組件產(chǎn)生的Span
Trace構(gòu)建的原理
簡(jiǎn)單來(lái)說(shuō)的話(huà)Trace是由眾多的Span組成的,而Span則是由眾多的Instrumentation庫(kù)組成的,這些庫(kù)由開(kāi)源作者構(gòu)建,用于支持不同的組件,如http請(qǐng)求,kafka,redis等等。依托于這些Instrumentation,調(diào)用鏈可以生成對(duì)應(yīng)的Span。
生成Span自然不是難題,問(wèn)題在于是如何將這些Span串聯(lián)起來(lái)的。在Trace中有一個(gè)唯一的標(biāo)識(shí)TraceID,而且在Span中也有一個(gè)SpanId和ParentSpanId,借助這些信息,在Span將所有數(shù)據(jù)推送到服務(wù)端后,服務(wù)端就能根據(jù)這些信息進(jìn)行重組,然后在界面上進(jìn)行展示。
但是又存在一個(gè)問(wèn)題,TraceId以及ParentSpanId是如何在Span間進(jìn)行傳遞的呢?
這里就涉及到了Trace的底層原理了。在這里以Java Sdk來(lái)舉例。在Sdk中會(huì)定義一個(gè)Context類(lèi)用于建立一個(gè)內(nèi)存中的線程隔離的存儲(chǔ)機(jī)制來(lái)存儲(chǔ)上游傳遞的數(shù)據(jù)。一般來(lái)說(shuō)上游往下游傳遞數(shù)據(jù)每個(gè)插件都是不同的形式。例如如果是http請(qǐng)求,那就借助Header,如果是Kafka,也是借助于Kafka自帶的prop來(lái)進(jìn)行數(shù)據(jù)傳遞。之后在下游獲取到數(shù)據(jù)后利用Context將其存放入內(nèi)存中,這個(gè)過(guò)程被稱(chēng)為extract,在數(shù)據(jù)要再往下傳遞時(shí),需要將內(nèi)存中數(shù)據(jù)取出,在解析成Header或是其他的形式,這個(gè)被稱(chēng)為inject。調(diào)用鏈信息正是以此來(lái)傳遞的。
Trace就是依靠traceparent來(lái)進(jìn)行傳遞的,traceparent不僅包含了traceId,還包含了一些isSample等等的基礎(chǔ)信息。
Metrics
Metrics是一種度量標(biāo)準(zhǔn),用于展現(xiàn)應(yīng)用的CPU,內(nèi)存等等指標(biāo)級(jí)的度量信息。
OpenTelemetry定義了三種metrics儀器:
- counter: 累加值,這類(lèi)指標(biāo)不會(huì)減少,只會(huì)不斷的累加上去
- measure: 一段時(shí)間的數(shù)據(jù)聚合值,表示的是一段時(shí)間內(nèi)的數(shù)據(jù)累加值
- observer: 抓取當(dāng)前時(shí)間的一系列特定值
實(shí)際上OpenTelemetry提供了許多基礎(chǔ)的指標(biāo)計(jì)算方式,例如:LongCounter,LongUpDownCounter,DoubleHistogram,DoubleGauge等等。
Meter meter = GlobalOpenTelemetry.meterBuilder("my-meter-instrumentation")
.setInstrumentationVersion("1.0.0")
.build();
LongCounter counter = meter
.counterBuilder("my_metrics")
.setDescription("My Metrics")
.setUnit("1")
.build();
counter.add(100);
上述代碼是一個(gè)簡(jiǎn)單的創(chuàng)建指標(biāo)的的例子,這里創(chuàng)建了一個(gè)固定值為100的名為my_metrics的指標(biāo),由于是counter,所以最終指標(biāo)名為my_metrics_total

Logs
日志也是OpenTelemetry的一大功能之一,不過(guò)截止到本文發(fā)布前,Logs功能還未GA,因此存在變數(shù),后續(xù)我們?cè)诹牡紸gent相關(guān)內(nèi)容時(shí)會(huì)再簡(jiǎn)單聊聊這部分內(nèi)容,在這里就先一筆帶過(guò)。
Baggage
Baggage用于在Span間傳遞數(shù)據(jù)。
設(shè)想一個(gè)場(chǎng)景,你希望在鏈路的當(dāng)前的Span中將某些數(shù)據(jù)傳遞下去,使用attributes顯示然是不行的,因此需要一些手段將其傳遞下去,Baggage就是為此而設(shè)計(jì)的。
其實(shí)Baggage的原理基本和調(diào)用鏈的traceId的傳遞基本相似,不同之處是它定義了一個(gè)名為baggage的key,而這個(gè)key中包含的值是以K-V形式組織的,因此你可以傳遞自己想要的值下去。
在早期Baggage底層維護(hù)了一個(gè)Map來(lái)存儲(chǔ)這些數(shù)據(jù),后來(lái)在某個(gè)版本后改成了用數(shù)組的形式,每?jī)蓚€(gè)數(shù)組位置分別存儲(chǔ)一對(duì)K-V,并且做了一些特殊的處理來(lái)實(shí)現(xiàn)刪除等操作,有興趣的可以去看看源碼。
總結(jié)
在本文中我們簡(jiǎn)單的介紹了OpenTelemetry的一些使用和實(shí)現(xiàn)的原理,在后續(xù)的文章中會(huì)更多的介紹整個(gè)OpenTelemetry的體系,請(qǐng)期待后續(xù)!
參考文檔:
以上就是OpenTelemetry初識(shí)及調(diào)用鏈Trace詳解的詳細(xì)內(nèi)容,更多關(guān)于OpenTelemetry Trace調(diào)用鏈的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java while(scanner.hasNext())無(wú)法跳出的解決方案
這篇文章主要介紹了Java while(scanner.hasNext())無(wú)法跳出的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
win10和win7下java開(kāi)發(fā)環(huán)境配置教程
這篇文章主要為大家詳細(xì)介紹了win7下Java開(kāi)發(fā)環(huán)境配置教程,win10下Java開(kāi)發(fā)環(huán)境配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
SpringCloud Gateway跨域配置代碼實(shí)例
這篇文章主要介紹了SpringCloud Gateway跨域配置代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
MyBatis使用級(jí)聯(lián)操作解決lombok構(gòu)造方法識(shí)別失敗問(wèn)題
這篇文章主要介紹了MyBatis使用級(jí)聯(lián)操作解決lombok構(gòu)造方法識(shí)別失敗問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Java實(shí)現(xiàn)的決策樹(shù)算法完整實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)的決策樹(shù)算法,簡(jiǎn)單描述了決策樹(shù)的概念、原理,并結(jié)合完整實(shí)例形式分析了java實(shí)現(xiàn)決策樹(shù)算法的相關(guān)操作技巧,代碼中備有較為詳盡的注釋便于理解,需要的朋友可以參考下2017-11-11
JeecgBoot框架升級(jí)至Spring?Boot3的實(shí)戰(zhàn)步驟
本文主要介紹了JeecgBoot框架升級(jí)至Spring?Boot3的實(shí)戰(zhàn)步驟,從?2.7.10升級(jí)到3.1.5有以下幾個(gè)點(diǎn)需要注意,下面就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-04-04
Spring Bean初始化及銷(xiāo)毀多種實(shí)現(xiàn)方式
這篇文章主要介紹了Spring Bean初始化及銷(xiāo)毀多種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
java實(shí)現(xiàn)爬蟲(chóng)爬網(wǎng)站圖片的實(shí)例代碼
這篇文章主要介紹了java實(shí)現(xiàn)爬蟲(chóng)爬網(wǎng)站圖片的實(shí)例代碼,需要的朋友可以參考下2018-06-06

