链路传播(Propagate)机制及使用场景

2023-11-03 06:31

本文主要是介绍链路传播(Propagate)机制及使用场景,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

服务间链路追踪传播机制是指在微服务架构中,通过记录和跟踪服务之间的请求和响应信息,来实现对服务间链路的追踪和监控。这种机制可以帮助开发人员快速定位服务间出现的问题,并进行优化和调整。

具体来说,服务间链路追踪传播机制可以通过在每个服务的请求和响应中添加唯一标识符来实现。当一个服务发送请求到另一个服务时,它会将自己的唯一标识符添加到请求头中,并发送给目标服务。目标服务收到请求后,会将请求头中的唯一标识符复制到响应头中,并返回给调用方。调用方收到响应后,可以通过唯一标识符来追踪服务间的调用链路。

为了实现服务间链路追踪传播机制,通常会使用一些开源工具或框架,比如OpenTelemetryDataDogZipkinSkyWalking 等。这些工具可以自动记录服务间的请求和响应信息,并提供可视化的界面来帮助开发人员进行调试和优化。

通常,我们会将这种特殊标识的请求头称之为传播协议

常见传播协议

不同的 APM 工具支持一种或者多种传播协议,以便更好的服务于应用。

  • b3
  • uber
  • sw8
  • w3c
  • datadog
  • ot

传播协议与APM产品

OpenTelemetry 除了原生支持 otw3cb3uber几种协议外,通过:opentelemetry-collector-contrib 支持 sw8datadog传播协议。

B3 协议

B3 传播协议是第一个链路追踪协议,这里重点讲述 B3 传播协议相关内容。

B3 有两种编码:Single Header 和 Multiple Header。

  • 多个标头编码 X-B3-在跟踪上下文中使用每个项目的前缀标头
  • 单个标头将上下文分隔为一个名为 b3. 提取字段时,单头变体优先于多头变体。

这是一个使用多个标头编码的示例流程,假设 HTTP 请求带有传播的跟踪:

在这里插入图片描述

Multiple Headers

需要配合多个 header key 使用。

TraceId

X-B3-TraceId 标头编码为 32 或 16 个低十六进制字符。例如,128 位 TraceId 标头:X-B3-TraceId:463ac35c9f6413ad48485a3953bb6124。除非仅传播“采样状态”,否则需要 X-B3-TraceId 标头。

SpanId

X-B3-SpanId 标头编码为16个较低的十六进制字符。例如:X-B3-SpanId:a2fb4a1d1a96d312。除非仅传播“采样状态”,否则需要 X-B3-SpanId 标头。

ParentSpanId

X-B3-ParentSpanId标头可能存在于子跨度上,而在根跨度上必须不存在。它被编码为16个低十六进制字符。例如,ParentSpanId标头可能看起来像:X-B3-ParentSpanId:00020000000000001

Sampling State

接受采样决定编码为X-B3-Sampled:1,拒绝编码为X-B3-Sampled:0。缺席意味着将决定推迟到该报头的接收方。例如:X-B3-Sampled:1

注:在编写本规范之前,一些示踪剂传播X-B3-Sampledtruefalse,而不是10。虽然您不应该将X-B3-Sampled编码为truefalse,但宽松的实现可能会接受它们。

Debug Flag

调试编码为X-B3-Flags: 1。可以忽略 Absent 或任何其他值。调试意味着接受决定,所以不要同时发送X-B3-Sampled标头。

Single Header

Single Header 只有一个header 为b3的 key 作为标记。

b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}

b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d

实现场景

目前主要有两大实现场景:

  • APM:主要是服务端之间传播
  • RUM:用户端(web、小程序等)与服务端传播

APM

OpenTelemetry 传播器源码

OpenTelemetry 传播器源码

传播器接口:TextMapPropagator

opentelemetry-java 通过定义统一的接口TextMapPropagator来实现各种 Propagate。

TextMapPropagator默认提供了一个空实现noop()

TextMapPropagator 定义了三个接口方法:

  • fields() : 规定了哪些字段作为传播器识别的对象
  • inject():定义了传播器字段注入规则
  • extract():定义传播器字段提取规则,返回Context对象

一些厂商有自己的 APM 标准,通过扩展TextMapPropagator接口,实现对接厂商特定的传播器。

@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 时,通过指定传播器类型来实现对应实例构造,通过方法得知,otel 支持同时指定多种类型的传播器协议,默认采用tracecontext传播器——即w3c标准的传播器:
List<String> requestedPropagators = config.getList("otel.propagators", DEFAULT_PROPAGATORS);

获取传播器

opentelemetry-java 通过OpenTelemetry对象的propagating()方法构造当前支持的传播器,且通过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 对象来实现 trace、metric 相关业务逻辑。比如,启动一个 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 通过单例模式实现获取全局OpenTelemetry对象。而OpenTelemetry则主要是通过AutoConfiguredOpenTelemetrySdkgetOpenTelemetrySdk方法初始化构建。

至此,基本上完成了opentelemetry-java从传播器实现、构造、初始化及获取相关操作。

DataDog(DDtrace)传播器源码

DataDog(DDtrace)传播器源码

DDtrace除了支持上面表格中的传播协议之外,还支持其他传播协议,如:

  • Haystack
    • Haystack-Trace-ID
    • Haystack-Span-ID
    • Haystack-Parent-ID
  • XRAY
    • X-Amzn-Trace-Id

RUM

前面了解了传播器的原理、作用以及实现方式。传播器提到最多的是 header,也就说大多数都是基于 http 协议。基于 http 协议,进一步引申到 html、小程序、Android、iOS 等,从而引出另一个概念:RUM

RUM 全称为 Real User Monitor,即真实用户监控。

RUM 通过实现不同的传播器协议,将用户端与服务端紧密联系在一起,我们知道 APM 是后端接口及服务的性能表现,但接口用于地方很多,有的来源于 web,有的来源于小程序等等。通过引入 RUM,结合链路分析,很容易追踪到数据来源于哪一端。

当然 RUM 的作用不仅仅是这一方面,目前市面上常见的是基于 W3C(万维网联盟)定义的[navigation-timing] 标准(见下图),该标准详细定义了各种浏览器事件,通过浏览器事件的简单计算就可以算出来前端页面的首屏、白屏、DOM 加载、HTML 加载等时长,相较于测试环境的 F12 检查者模式,能有更效的收集生产环境真实用户的前端体验。

在这里插入图片描述

opentelemetry-js

opentelemetry-js是 OpenTelemetry 具体的实现,目前支持 nodejs 和 js,作用范围比较有限,主要围绕 OpenTelemetry 的集中传播器协议实现,产品成熟度仍需进一步提升。

opentelemetry-js demo

观测云 RUM

是观测云推出的 RUM 产品,可以与多种 APM 技术融合,本质上是支持多种传播器协议,进而能够与各种 APM 结合使用。

支持的传播器协议有:

  • 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三种类型的配置方式,根据在 rum sdk 中配置的 traceType 类型 添加对应的 header。

目前支持的应用有:

  • 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: '<应用 ID>',datakitOrigin: '<DATAKIT ORIGIN>', // 协议(包括://),域名(或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头部的所有请求列表。可以是请求的origin,也可以是是正则})
</script>

其中 traceType 为传播器协议。

通过 CDN 加速缓存,以同步脚本引入的方式引入 SDK,此方式可以确保能够收集到所有的错误,资源,请求,性能指标,不过可能会影响页面的加载性能。
单页应用建议将下方代码复制粘贴到 html 文件 head 标签的首行,多页应用建议将下方代码复制粘贴到公共 head 模版文件的首行。

更多更详细接入方式,参考官方文档用户访问监测

RUM 与 APM 实际应用

以某开源平台访问用户资源为例,查看实际应用场景,如下图所示:

在这里插入图片描述

右侧服务列表:

  • browser :浏览器链路,由RUM端上报
  • 其他服务为 APM 上报

RUM 通过传递特定的 header 信息,将链路信息传递给 APM,APM 正确解析相关传播协议,并以 RUM 传递的 trace为基准,创建 span信息,实现前后端串联。
browser同时可以查看当前用户的请求耗时分布(128.3 ms)
通过右上角view 按钮,可以跳转到当前页面的查看器,以此来查看分析用户行为。如下图所示

在这里插入图片描述

Baggage

前面已经分析了传播器一般需要携带的参数信息,便于前后端的应用串联,但实际上这只是传播器的基础用法,通常都是用于传递 trace 信息,实际上,在上面 opentelemetry-java 源码分析中,有一个频繁出现的单词baggagebaggage主要作用于业务参数的传递。
通常,研发人员不会直接在接口上填上用户信息,所以无法准确追踪这个接口是哪个用户触发的,借助 baggage,通过 header 进行传递,能够在服务间无限传递下去。

OpenTelemetry Baggage 用法

Header key 以ot-baggage- 开头的参数,会被 OpenTelemetry 一直传递下去。

还可以采用sdk的方式:OpenTelemetry Baggage SDK用法

DataDog Baggage 用法

需要配置启动参数dd.trace.header.baggage,设置哪些header 作为传递的参数依据。用法如下:

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 用法

这篇关于链路传播(Propagate)机制及使用场景的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/336258

相关文章

使用Vue-ECharts实现数据可视化图表功能

《使用Vue-ECharts实现数据可视化图表功能》在前端开发中,经常会遇到需要展示数据可视化的需求,比如柱状图、折线图、饼图等,这类需求不仅要求我们准确地将数据呈现出来,还需要兼顾美观与交互体验,所... 目录前言为什么选择 vue-ECharts?1. 基于 ECharts,功能强大2. 更符合 Vue

如何合理使用Spring的事务方式

《如何合理使用Spring的事务方式》:本文主要介绍如何合理使用Spring的事务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、底层构造1.1.事务管理器1.2.事务定义信息1.3.事务状态1.4.联系1.2、特点1.3、原理2. Sprin

Vue中插槽slot的使用示例详解

《Vue中插槽slot的使用示例详解》:本文主要介绍Vue中插槽slot的使用示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、插槽是什么二、插槽分类2.1 匿名插槽2.2 具名插槽2.3 作用域插槽三、插槽的基本使用3.1 匿名插槽

使用WPF实现窗口抖动动画效果

《使用WPF实现窗口抖动动画效果》在用户界面设计中,适当的动画反馈可以提升用户体验,尤其是在错误提示、操作失败等场景下,窗口抖动作为一种常见且直观的视觉反馈方式,常用于提醒用户注意当前状态,本文将详细... 目录前言实现思路概述核心代码实现1、 获取目标窗口2、初始化基础位置值3、创建抖动动画4、动画完成后

PyQt5 QDate类的具体使用

《PyQt5QDate类的具体使用》QDate是PyQt5中处理日期的核心类,本文主要介绍了PyQt5QDate类的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录核心功能常用方法及代码示例​1. 创建日期对象​2. 获取日期信息​3. 日期计算与比较​4. 日

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

使用Java将实体类转换为JSON并输出到控制台的完整过程

《使用Java将实体类转换为JSON并输出到控制台的完整过程》在软件开发的过程中,Java是一种广泛使用的编程语言,而在众多应用中,数据的传输和存储经常需要使用JSON格式,用Java将实体类转换为J... 在软件开发的过程中,Java是一种广泛使用的编程语言,而在众多应用中,数据的传输和存储经常需要使用j

Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例

《Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例》本文介绍Nginx+Keepalived实现Web集群高可用负载均衡的部署与测试,涵盖架构设计、环境配置、健康检查、... 目录前言一、架构设计二、环境准备三、案例部署配置 前端 Keepalived配置 前端 Nginx

Python logging模块使用示例详解

《Pythonlogging模块使用示例详解》Python的logging模块是一个灵活且强大的日志记录工具,广泛应用于应用程序的调试、运行监控和问题排查,下面给大家介绍Pythonlogging模... 目录一、为什么使用 logging 模块?二、核心组件三、日志级别四、基本使用步骤五、快速配置(bas

使用animation.css库快速实现CSS3旋转动画效果

《使用animation.css库快速实现CSS3旋转动画效果》随着Web技术的不断发展,动画效果已经成为了网页设计中不可或缺的一部分,本文将深入探讨animation.css的工作原理,如何使用以及... 目录1. css3动画技术简介2. animation.css库介绍2.1 animation.cs