重慶網(wǎng)站建設(shè)合肥公司網(wǎng)站怎么宣傳
服務(wù)間鏈路追蹤傳播機制是指在微服務(wù)架構(gòu)中,通過記錄和跟蹤服務(wù)之間的請求和響應(yīng)信息,來實現(xiàn)對服務(wù)間鏈路的追蹤和監(jiān)控。這種機制可以幫助開發(fā)人員快速定位服務(wù)間出現(xiàn)的問題,并進行優(yōu)化和調(diào)整。
具體來說,服務(wù)間鏈路追蹤傳播機制可以通過在每個服務(wù)的請求和響應(yīng)中添加唯一標(biāo)識符來實現(xiàn)。當(dāng)一個服務(wù)發(fā)送請求到另一個服務(wù)時,它會將自己的唯一標(biāo)識符添加到請求頭中,并發(fā)送給目標(biāo)服務(wù)。目標(biāo)服務(wù)收到請求后,會將請求頭中的唯一標(biāo)識符復(fù)制到響應(yīng)頭中,并返回給調(diào)用方。調(diào)用方收到響應(yīng)后,可以通過唯一標(biāo)識符來追蹤服務(wù)間的調(diào)用鏈路。
為了實現(xiàn)服務(wù)間鏈路追蹤傳播機制,通常會使用一些開源工具或框架,比如
OpenTelemetry
、DataDog
、Zipkin
、SkyWalking
等。這些工具可以自動記錄服務(wù)間的請求和響應(yīng)信息,并提供可視化的界面來幫助開發(fā)人員進行調(diào)試和優(yōu)化。
通常,我們會將這種特殊標(biāo)識的請求頭稱之為傳播協(xié)議。
常見傳播協(xié)議
不同的 APM 工具支持一種或者多種傳播協(xié)議,以便更好的服務(wù)于應(yīng)用。
- b3
- uber
- sw8
- w3c
- datadog
- ot
OpenTelemetry 除了原生支持 ot
、w3c
、 b3
和uber
幾種協(xié)議外,通過:opentelemetry-collector-contrib
支持 sw8
和datadog
傳播協(xié)議。
B3 協(xié)議
B3 傳播協(xié)議是第一個鏈路追蹤協(xié)議,這里重點講述 B3 傳播協(xié)議相關(guān)內(nèi)容。
B3 有兩種編碼:Single Header 和 Multiple Header。
- 多個標(biāo)頭編碼 X-B3-在跟蹤上下文中使用每個項目的前綴標(biāo)頭
- 單個標(biāo)頭將上下文分隔為一個名為 b3. 提取字段時,單頭變體優(yōu)先于多頭變體。
這是一個使用多個標(biāo)頭編碼的示例流程,假設(shè) HTTP 請求帶有傳播的跟蹤:
Multiple Headers
需要配合多個 header key 使用。
TraceId
X-B3-TraceId
標(biāo)頭編碼為 32 或 16 個低十六進制字符。例如,128 位 TraceId 標(biāo)頭:X-B3-TraceId:463ac35c9f6413ad48485a3953bb6124
。除非僅傳播“采樣狀態(tài)”,否則需要 X-B3-TraceId
標(biāo)頭。
SpanId
X-B3-SpanId
標(biāo)頭編碼為16個較低的十六進制字符。例如:X-B3-SpanId:a2fb4a1d1a96d312
。除非僅傳播“采樣狀態(tài)”,否則需要 X-B3-SpanId
標(biāo)頭。
ParentSpanId
X-B3-ParentSpanId
標(biāo)頭可能存在于子跨度上,而在根跨度上必須不存在。它被編碼為16個低十六進制字符。例如,ParentSpanId
標(biāo)頭可能看起來像:X-B3-ParentSpanId:00020000000000001
Sampling State
接受采樣決定編碼為X-B3-Sampled:1
,拒絕編碼為X-B3-Sampled:0
。缺席意味著將決定推遲到該報頭的接收方。例如:X-B3-Sampled:1
。
注:在編寫本規(guī)范之前,一些示蹤劑傳播X-B3-Sampled
為true
或false
,而不是1
或0
。雖然您不應(yīng)該將X-B3-Sampled
編碼為true
或false
,但寬松的實現(xiàn)可能會接受它們。
Debug Flag
調(diào)試編碼為X-B3-Flags: 1
??梢院雎?Absent 或任何其他值。調(diào)試意味著接受決定,所以不要同時發(fā)送X-B3-Sampled
標(biāo)頭。
Single Header
Single Header
只有一個header 為b3
的 key 作為標(biāo)記。
b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}
如
b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d
實現(xiàn)場景
目前主要有兩大實現(xiàn)場景:
- APM:主要是服務(wù)端之間傳播
- RUM:用戶端(web、小程序等)與服務(wù)端傳播
APM
OpenTelemetry 傳播器源碼
OpenTelemetry 傳播器源碼
傳播器接口:TextMapPropagator
opentelemetry-java
通過定義統(tǒng)一的接口TextMapPropagator
來實現(xiàn)各種 Propagate。
TextMapPropagator
默認提供了一個空實現(xiàn)noop()
。
TextMapPropagator
定義了三個接口方法:
- fields() : 規(guī)定了哪些字段作為傳播器識別的對象
- inject():定義了傳播器字段注入規(guī)則
- extract():定義傳播器字段提取規(guī)則,返回
Context
對象
一些廠商有自己的 APM 標(biāo)準(zhǔn),通過擴展TextMapPropagator
接口,實現(xiàn)對接廠商特定的傳播器。
@ThreadSafe
public interface TextMapPropagator {static TextMapPropagator composite(TextMapPropagator... propagators) {return composite(Arrays.asList(propagators));}static TextMapPropagator composite(Iterable<TextMapPropagator> propagators) {List<TextMapPropagator> propagatorsList = new ArrayList<>();for (TextMapPropagator propagator : propagators) {propagatorsList.add(propagator);}if (propagatorsList.isEmpty()) {return NoopTextMapPropagator.getInstance();}if (propagatorsList.size() == 1) {return propagatorsList.get(0);}return new MultiTextMapPropagator(propagatorsList);}static TextMapPropagator noop() {return NoopTextMapPropagator.getInstance();}Collection<String> fields();<C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter);<C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter);
}
OtTracePropagator
OtTracePropagator
是 OpenTelemetry 特有的傳播器,識別解析ot-
開頭的header,部分源碼如下:
@Immutable
public final class OtTracePropagator implements TextMapPropagator {static final String TRACE_ID_HEADER = "ot-tracer-traceid";static final String SPAN_ID_HEADER = "ot-tracer-spanid";static final String SAMPLED_HEADER = "ot-tracer-sampled";static final String PREFIX_BAGGAGE_HEADER = "ot-baggage-";private static final Collection<String> FIELDS =Collections.unmodifiableList(Arrays.asList(TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER));....@Overridepublic Collection<String> fields() {return FIELDS;}@Overridepublic <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {if (context == null || setter == null) {return;}SpanContext spanContext = Span.fromContext(context).getSpanContext();if (!spanContext.isValid()) {return;}// Lightstep trace id MUST be 64-bits therefore OpenTelemetry trace id is truncated to 64-bits// by retaining least significant (right-most) bits.setter.set(carrier, TRACE_ID_HEADER, spanContext.getTraceId().substring(TraceId.getLength() / 2));setter.set(carrier, SPAN_ID_HEADER, spanContext.getSpanId());setter.set(carrier, SAMPLED_HEADER, String.valueOf(spanContext.isSampled()));// Baggage is only injected if there is a valid SpanContextBaggage baggage = Baggage.fromContext(context);if (!baggage.isEmpty()) {// Metadata is not supported by OpenTracingbaggage.forEach((key, baggageEntry) ->setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, baggageEntry.getValue()));}}@Overridepublic <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) {if (context == null) {return Context.root();}if (getter == null) {return context;}String incomingTraceId = getter.get(carrier, TRACE_ID_HEADER);String traceId =incomingTraceId == null? TraceId.getInvalid(): StringUtils.padLeft(incomingTraceId, MAX_TRACE_ID_LENGTH);String spanId = getter.get(carrier, SPAN_ID_HEADER);String sampled = getter.get(carrier, SAMPLED_HEADER);SpanContext spanContext = buildSpanContext(traceId, spanId, sampled);if (!spanContext.isValid()) {return context;}Context extractedContext = context.with(Span.wrap(spanContext));// Baggage is only extracted if there is a valid SpanContextif (carrier != null) {BaggageBuilder baggageBuilder = Baggage.builder();for (String key : getter.keys(carrier)) {if (!key.startsWith(PREFIX_BAGGAGE_HEADER)) {continue;}String value = getter.get(carrier, key);if (value == null) {continue;}baggageBuilder.put(key.replace(OtTracePropagator.PREFIX_BAGGAGE_HEADER, ""), value);}Baggage baggage = baggageBuilder.build();if (!baggage.isEmpty()) {extractedContext = extractedContext.with(baggage);}}return extractedContext;}...
}
初始化傳播器
opentelemetry-java
通過PropagatorConfiguration
對象對Propagate
進行初始化操作,源碼如下:
final class PropagatorConfiguration {private static final List<String> DEFAULT_PROPAGATORS = Arrays.asList("tracecontext", "baggage");static ContextPropagators configurePropagators(ConfigProperties config,ClassLoader serviceClassLoader,BiFunction<? super TextMapPropagator, ConfigProperties, ? extends TextMapPropagator>propagatorCustomizer) {Set<TextMapPropagator> propagators = new LinkedHashSet<>();List<String> requestedPropagators = config.getList("otel.propagators", DEFAULT_PROPAGATORS);NamedSpiManager<TextMapPropagator> spiPropagatorsManager =SpiUtil.loadConfigurable(ConfigurablePropagatorProvider.class,ConfigurablePropagatorProvider::getName,ConfigurablePropagatorProvider::getPropagator,config,serviceClassLoader);if (requestedPropagators.contains("none")) {if (requestedPropagators.size() > 1) {throw new ConfigurationException("otel.propagators contains 'none' along with other propagators");}return ContextPropagators.noop();}for (String propagatorName : requestedPropagators) {propagators.add(propagatorCustomizer.apply(getPropagator(propagatorName, spiPropagatorsManager), config));}return ContextPropagators.create(TextMapPropagator.composite(propagators));}private static TextMapPropagator getPropagator(String name, NamedSpiManager<TextMapPropagator> spiPropagatorsManager) {if (name.equals("tracecontext")) {return W3CTraceContextPropagator.getInstance();}if (name.equals("baggage")) {return W3CBaggagePropagator.getInstance();}TextMapPropagator spiPropagator = spiPropagatorsManager.getByName(name);if (spiPropagator != null) {return spiPropagator;}throw new ConfigurationException("Unrecognized value for otel.propagators: "+ name+ ". Make sure the artifact including the propagator is on the classpath.");}private PropagatorConfiguration() {}
}
啟動 agent 時,通過指定傳播器類型來實現(xiàn)對應(yīng)實例構(gòu)造,通過方法得知,otel 支持同時指定多種類型的傳播器協(xié)議,默認采用tracecontext
傳播器——即w3c
標(biāo)準(zhǔn)的傳播器:
List<String> requestedPropagators = config.getList("otel.propagators", DEFAULT_PROPAGATORS);
獲取傳播器
opentelemetry-java
通過OpenTelemetry
對象的propagating()
方法構(gòu)造當(dāng)前支持的傳播器,且通過getPropagators()
方法來獲取已有的傳播器,OpenTelemetry
是一個接口類,部分代碼如下:
public interface OpenTelemetry {static OpenTelemetry noop() {return DefaultOpenTelemetry.getNoop();}static OpenTelemetry propagating(ContextPropagators propagators) {return DefaultOpenTelemetry.getPropagating(propagators);}TracerProvider getTracerProvider();ContextPropagators getPropagators();...
}
每一個Instrumente
都需要通過InstrumenterBuilder
獲取OpenTelemetry
對象來實現(xiàn) trace、metric 相關(guān)業(yè)務(wù)邏輯。比如,啟動一個 span。
如何獲取OpenTelemetry
對象
通過上面的研究,我們知道如何獲取傳播器,但是獲取傳播器需要獲取OpenTelemetry
對象,opentelemetry-java
提供了一個全局的對象GlobalOpenTelemetry
來獲取OpenTelemetry
對象。我們來看看具體代碼
public final class GlobalOpenTelemetry {
...@Nullableprivate static volatile ObfuscatedOpenTelemetry globalOpenTelemetry;public static OpenTelemetry get() {OpenTelemetry openTelemetry = globalOpenTelemetry;if (openTelemetry == null) {synchronized(mutex) {openTelemetry = globalOpenTelemetry;if (openTelemetry == null) {OpenTelemetry autoConfigured = maybeAutoConfigureAndSetGlobal();if (autoConfigured != null) {return autoConfigured;}set(OpenTelemetry.noop());return OpenTelemetry.noop();}}}return openTelemetry;}public static void set(OpenTelemetry openTelemetry) {synchronized(mutex) {if (globalOpenTelemetry != null) {throw new IllegalStateException("GlobalOpenTelemetry.set has already been called. GlobalOpenTelemetry.set must be called only once before any calls to GlobalOpenTelemetry.get. If you are using the OpenTelemetrySdk, use OpenTelemetrySdkBuilder.buildAndRegisterGlobal instead. Previous invocation set to cause of this exception.", setGlobalCaller);} else {globalOpenTelemetry = new ObfuscatedOpenTelemetry(openTelemetry);setGlobalCaller = new Throwable();}}}@Nullableprivate static OpenTelemetry maybeAutoConfigureAndSetGlobal() {Class openTelemetrySdkAutoConfiguration;try {openTelemetrySdkAutoConfiguration = Class.forName("io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk");} catch (ClassNotFoundException var7) {return null;}boolean globalAutoconfigureEnabled = Boolean.parseBoolean(ConfigUtil.getString("otel.java.global-autoconfigure.enabled", "false"));if (!globalAutoconfigureEnabled) {logger.log(Level.INFO, "AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled. To enable, run your JVM with -Dotel.java.global-autoconfigure.enabled=true");return null;} else {try {Method initialize = openTelemetrySdkAutoConfiguration.getMethod("initialize");Object autoConfiguredSdk = initialize.invoke((Object)null);Method getOpenTelemetrySdk = openTelemetrySdkAutoConfiguration.getMethod("getOpenTelemetrySdk");return new ObfuscatedOpenTelemetry((OpenTelemetry)getOpenTelemetrySdk.invoke(autoConfiguredSdk));} catch (IllegalAccessException | NoSuchMethodException var5) {throw new IllegalStateException("AutoConfiguredOpenTelemetrySdk detected on classpath but could not invoke initialize method. This is a bug in OpenTelemetry.", var5);} catch (InvocationTargetException var6) {logger.log(Level.SEVERE, "Error automatically configuring OpenTelemetry SDK. OpenTelemetry will not be enabled.", var6.getTargetException());return null;}}}...
GlobalOpenTelemetry
通過單例模式實現(xiàn)獲取全局OpenTelemetry
對象。而OpenTelemetry
則主要是通過AutoConfiguredOpenTelemetrySdk
的 getOpenTelemetrySdk
方法初始化構(gòu)建。
至此,基本上完成了opentelemetry-java
從傳播器實現(xiàn)、構(gòu)造、初始化及獲取相關(guān)操作。
DataDog(DDtrace)傳播器源碼
DataDog(DDtrace)傳播器源碼
DDtrace除了支持上面表格中的傳播協(xié)議之外,還支持其他傳播協(xié)議,如:
- Haystack
- Haystack-Trace-ID
- Haystack-Span-ID
- Haystack-Parent-ID
- XRAY
- X-Amzn-Trace-Id
RUM
前面了解了傳播器的原理、作用以及實現(xiàn)方式。傳播器提到最多的是 header,也就說大多數(shù)都是基于 http 協(xié)議?;?http 協(xié)議,進一步引申到 html、小程序、Android、iOS 等,從而引出另一個概念:RUM。
RUM 全稱為
Real User Monitor
,即真實用戶監(jiān)控。
RUM 通過實現(xiàn)不同的傳播器協(xié)議,將用戶端與服務(wù)端緊密聯(lián)系在一起,我們知道 APM 是后端接口及服務(wù)的性能表現(xiàn),但接口用于地方很多,有的來源于 web,有的來源于小程序等等。通過引入 RUM,結(jié)合鏈路分析,很容易追蹤到數(shù)據(jù)來源于哪一端。
當(dāng)然 RUM 的作用不僅僅是這一方面,目前市面上常見的是基于 W3C(萬維網(wǎng)聯(lián)盟)定義的[navigation-timing] 標(biāo)準(zhǔn)(見下圖),該標(biāo)準(zhǔn)詳細定義了各種瀏覽器事件,通過瀏覽器事件的簡單計算就可以算出來前端頁面的首屏、白屏、DOM 加載、HTML 加載等時長,相較于測試環(huán)境的 F12 檢查者模式,能有更效的收集生產(chǎn)環(huán)境真實用戶的前端體驗。
opentelemetry-js
opentelemetry-js是 OpenTelemetry 具體的實現(xiàn),目前支持 nodejs 和 js,作用范圍比較有限,主要圍繞 OpenTelemetry 的集中傳播器協(xié)議實現(xiàn),產(chǎn)品成熟度仍需進一步提升。
opentelemetry-js
demo
觀測云 RUM
是觀測云推出的 RUM 產(chǎn)品,可以與多種 APM 技術(shù)融合,本質(zhì)上是支持多種傳播器協(xié)議,進而能夠與各種 APM 結(jié)合使用。
支持的傳播器協(xié)議有:
- ddtrace :x-datadog-parent-id,x-datadog-sampled,x-datadog-sampling-priority,x-datadog-trace-id。
- skywalking: sw8。
- jaeger: uber-trace-id。
- zipkin: X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId、X-B3-Sampled、X-B3-Flags。
- zipkin_single_header: b3。
- w3c_traceparent: traceparent。
- opentelemetry: 該類型支持 zipkin_single_header,w3c_traceparent,zipkin、jaeger三種類型的配置方式,根據(jù)在 rum sdk 中配置的 traceType 類型 添加對應(yīng)的 header。
目前支持的應(yīng)用有:
- web H5
- 小程序
- Android
- iOS
- React Native
- Flutter
- UniApp
- C++
以 web 為例,展示接入方式,接入簡單。
<script src="https://static.guance.com/browser-sdk/v3/dataflux-rum.js" type="text/javascript"></script>
<script>window.DATAFLUX_RUM &&window.DATAFLUX_RUM.init({applicationId: '<應(yīng)用 ID>',datakitOrigin: '<DATAKIT ORIGIN>', // 協(xié)議(包括://),域名(或IP地址)[和端口號]env: 'production',version: '1.0.0',service: 'browser',sessionSampleRate: 100,sessionReplaySampleRate: 70,trackInteractions: true,traceType: 'ddtrace', // 非必填,默認為ddtrace,目前支持 ddtrace、zipkin、skywalking_v3、jaeger、zipkin_single_header、w3c_traceparent 6種類型allowedTracingOrigins: ['https://api.example.com',/https:\/\/.*\.my-api-domain\.com/], // 非必填,允許注入trace采集器所需header頭部的所有請求列表??梢允钦埱蟮膐rigin,也可以是是正則})
</script>
其中 traceType 為傳播器協(xié)議。
通過 CDN 加速緩存,以同步腳本引入的方式引入 SDK,此方式可以確保能夠收集到所有的錯誤,資源,請求,性能指標(biāo),不過可能會影響頁面的加載性能。
單頁應(yīng)用建議將下方代碼復(fù)制粘貼到 html 文件 head 標(biāo)簽的首行,多頁應(yīng)用建議將下方代碼復(fù)制粘貼到公共 head 模版文件的首行。
更多更詳細接入方式,參考官方文檔用戶訪問監(jiān)測
RUM 與 APM 實際應(yīng)用
以某開源平臺訪問用戶資源為例,查看實際應(yīng)用場景,如下圖所示:
右側(cè)服務(wù)列表:
- browser :瀏覽器鏈路,由RUM端上報
- 其他服務(wù)為 APM 上報
RUM 通過傳遞特定的 header 信息,將鏈路信息傳遞給 APM,APM 正確解析相關(guān)傳播協(xié)議,并以 RUM 傳遞的 trace為基準(zhǔn),創(chuàng)建 span信息,實現(xiàn)前后端串聯(lián)。
browser同時可以查看當(dāng)前用戶的請求耗時分布(128.3 ms)
通過右上角view 按鈕,可以跳轉(zhuǎn)到當(dāng)前頁面的查看器,以此來查看分析用戶行為。如下圖所示
Baggage
前面已經(jīng)分析了傳播器一般需要攜帶的參數(shù)信息,便于前后端的應(yīng)用串聯(lián),但實際上這只是傳播器的基礎(chǔ)用法,通常都是用于傳遞 trace 信息,實際上,在上面 opentelemetry-java
源碼分析中,有一個頻繁出現(xiàn)的單詞baggage
,baggage
主要作用于業(yè)務(wù)參數(shù)的傳遞。
通常,研發(fā)人員不會直接在接口上填上用戶信息,所以無法準(zhǔn)確追蹤這個接口是哪個用戶觸發(fā)的,借助 baggage,通過 header 進行傳遞,能夠在服務(wù)間無限傳遞下去。
OpenTelemetry Baggage 用法
Header key 以ot-baggage-
開頭的參數(shù),會被 OpenTelemetry 一直傳遞下去。
還可以采用sdk的方式:OpenTelemetry Baggage SDK用法
DataDog Baggage 用法
需要配置啟動參數(shù)dd.trace.header.baggage,設(shè)置哪些header 作為傳遞的參數(shù)依據(jù)。用法如下:
Environment Variable: DD_TRACE_HEADER_BAGGAGE
Default: null
Example: CASE-insensitive-Header:my-baggage-name,User-ID:userId,My-Header-And-Baggage-Name
Accepts a map of case-insensitive header keys to baggage keys and automatically applies matching request header values as baggage on traces. On propagation the reverse mapping is applied: Baggage is mapped to headers.
Available since version 1.3.0.
也可以采用 SDK 的方式:DataDog Baggage SDK 用法