前言OpenTelemetry是一个支持Java、Golang、Python等多种语言的分布式追踪项目,由于笔者的主要语言是Java,后续需要为介绍OpenTelemetryJavaAgent的实现,所以后续文章中的相关知识点主要基于Java或JavaSdk。在OpenTelemetry广泛应用于微服务的当下,对整个微服务系统的使用情况、服务依赖调用的观察已经不再像以前那样清晰。而这正是OpenTelemetry可以为我们提供的。OpenTelemetry源自OpenSencuc和OpenTracing的合并。它的目标是集成Trace、Metrics和Logging功能以提供可观察性。过去分布式追踪往往各行其是,没有固定的标准。每个分布式跟踪方案都显示出自己的魔力,使用不同的协议和标准。但是OpenTelemetry不同,它提供了一系列的标准,其可插拔的架构为以后的协议和数据结构的扩展提供了方便的途径。CallchainTrace分布式调用链,俗称调用链,用于记录请求路径的整体路径。下图是一个典型的请求及其RPC调用环节:从图中我们可以清楚地了解到这个请求刚刚是怎么经过的,经过了哪些组件、服务、接口。这就是调用链的作用之一,让我们的请求环节更加透明清晰。SpanSpan是调用链中的一个基本单元,一个调用链是由许多Span组成的。一个Span会包含如下信息:父Span的名称ID,根节点的父Span为空起止时间戳SpanContextAttributesSpaneventSpanLinksSpanstateSpanKind下面是一个典型的Span结构:{"trace_id":"7bba9f33312b3dbb8b2c2c62bb7abe2d","parent_id":"","span_id":"086e83747d0e381e","name":"/v1/sys/health","start_time":"2021-10-2216:04:01.209458162+0000UTC","end_time":"2021-10-2216:04:01.209514132+0000UTC","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":"领事健康检查","http.scheme":"http","http.host":"10.177.2.152:26040","http.flavor":"1.1"},"events":[{"name":"","message":"OK","timestamp":"2021-10-2216:04:01.209512872+0000UTC"}]}SpanContextSpanContext可以理解为context,是Span中包含的不可变对象。SpanContext包含:traceId:调用链IDspanId:IDtraceFlagofSpan:二进制调用链标志,一般用来表示调用链是否被采样(isSampled)traceState:携带调用链信息的K-V结构体列表信息。在javasdk中,你可以使用:Span.current().setAttribute("MyAttributes","attr");自定义您要设置的属性。当然,OpenTelemetry中默认内置的Instrumentation会定义一些指定的、标准化的Attributes。详情请参考【语义属性】(https://opentelemetry.io/docs...)在调用链页面上。下面的例子:Span.current().addEvent("MyEvent");SpanLinksSpanLinks是一种可以关联调用链的技术。通过配置关联的Span,可以在页面展示关联的调用链信息。但是请注意,SpanLinks必须在创建Span时添加,不像Events和Attributes可以在创建Span后添加。示例如下:Tracertracer=GlobalOpenTelemetry.getTracer("1111");Spanspan=tracer.spanBuilder("start").addLink(SpanContext.create("ee868088dfd10adbaa459c9aa353b112","53b11b6c55010604",TraceStateFlags()())).startSpan();span.end();SpanstatusSpan状态(Status)是定义span的状态,如下:UnsetOkErrorSpanKindSpanKind是指span的类型,如下:ServerClientProducerConsumerInternal顾名思义Server/Client指的是服务器/客户端,Producer/Consumer指的是生产者/消费者。显然,这一般适用于消息队列。internal是内部组件生成SpanTrace构建的原理。简单的说Trace是由很多Span组成的Span,而Span又是由无数的Instrumentation库组成的,这些Instrumentation库是开源作者为了支持不同的组件而构建的,比如http请求,kafka,redis等等。依靠这些Instrumentation,调用链可以生成对应的Span。生成Span自然不是问题,问题是如何将这些Span串联起来。Trace中有唯一标识TraceID,Span中也有SpanId和ParentSpanId。有了这些信息,Span将所有的数据推送到服务器后,服务器就可以根据这些信息进行重组,然后在界面展示上执行。但是还有一个问题,TraceId和ParentSpanId是如何在Spans之间传递的呢?这就涉及到Trace的底层原理。这里以JavaSdk为例。Sdk中定义了一个Context类,用于建立内存中线程隔离的存储机制,用于存储上游传递的数据。一般来说,每个插件在从上游到下游传输数据时,都是以不同的形式存在的。比如如果是http请求就用Header,如果是Kafka就用Kafka自带的props来传递数据。下游获取到数据后,使用Context存储到内存中。这个过程称为提取。当要向下传递数据时,需要将内存中的数据取出来,解析成Header或者其他形式。这称为注入。调用链信息就是这样传递的。Trace由traceparent传输。TraceParent不仅包含traceId,还包含一些基本信息,比如isSample。MetricsMetrics是一个指标,用于呈现应用程序级别的指标信息,例如CPU和内存。OpenTelemetry定义了三个metricsinstruments:counter:累计值,此类指标不会减少,只会不断累加measure:一段时间内的数据聚合值,表示一段时间内数据的累计值observer:抓取current时间的一系列具体值事实上,OpenTelemetry提供了很多基本的指标计算方法,例如:LongCounter、LongUpDownCounter、DoubleHistogram、DoubleGauge等。仪表meter=GlobalOpenTelemetry.meterBuilder("my-meter-instrumentation").setInstrumentationVersion("1.0.0").build();LongCountercounter=meter.counterBuilder("my_metrics").setDescription("MyMetrics").setUnit("1").build();counter.add(100);上面的代码是一个创建指标的简单示例。这里,创建了一个名为my_metrics的指标,其固定值为100。既然是计数器,那么最终的指标名称就是my_metrics_totalLogs。日志也是OpenTelemetry的主要功能之一。不过截至发文,Logs功能还未GA,因此存在变数。我们在讲Agent相关内容的时候会简单的讲一下这部分内容。先放在这里吧。BaggageBaggage是用来在Span之间传递数据的。想象一下您想要在链接的当前范围内传递一些数据的场景。不可能用属性来展示,所以需要一些手段来传递。行李就是为此目的而设计的。其实Baggage的原理和调用链中traceId的传递基本类似,不同的是它定义了一个名为baggage的key,这个key包含的values以K-V的形式组织起来,所以你可以通过你想要的值下降。Baggage早期在底层维护了一个Map来存放这些数据。后来到了某个版本之后,改为数组的形式,在数组的两个位置分别存储一对K-V,并做了一些特殊的处理,实现了删除等操作,有兴趣的可以去看看源码。小结在本文中,我们简要介绍了OpenTelemetry的一些使用和实现原理。在后续文章中,我们将介绍整个OpenTelemetry系统。敬请期待后续!参考文档:[1]https://opentelemetry.io/docs
