前言
本文基于SkyWalking8.6.0分析SkyWalking客户端(javaagent)的tracing部分。
- 理解SkyWalking中trace相关模型;
- trace跨进程传播原理;
- trace跨线程传播原理;
- 跨线程span开启和停止;
一、Segment和Span

1、TraceSegment
A {@link TraceSegment} means the segment, which exists in current {@link Thread}. And the distributed trace is formed by multi {@link TraceSegment}s, because the distributed trace crosses multi-processes, multi-threads.
在OpenTelemetry中Trace由n个Span构成;
在SkyWalking中Trace由m个Segment构成,每个Segment包含n个Span。
每个Segment包含一个线程中的Span。
当TraceSegment构造时,会分配全局唯一segmentId和traceId,链路中第一个Segment的traceId是链路的traceId。

GlobalIdGenerator#generate:segmentId和traceId的生成算法。
id=进程id(uuid).线程id.线程自增序列。
整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
其中线程自增序列(nextSeq)=时间戳+4位自增序列(0-9999) 。
即通过segmentId和traceId可以反向定位segment和trace生成时间。

TraceSegment#relatedGlobalTrace:
如果当前Segment非链路中第一个Segment,traceId会被覆盖为链路上下文中的traceId。

2、AbstractTracingSpan
AbstractTracingSpan是Span的抽象实现,包含了众多关键属性。
- spanId:span在segment下的唯一标识,从0开始自增;
- parentSpanId:父span的id,-1代表当前span是segment中的第一个span;
- operationName:操作名,如SpringMVC的{POST}/api/v1/x,RabbitMQ消费者的RabbitMQ/Topic/{exchange}/Queue/{routingKey}/Consumer;
- tags:标签,如http请求有http.method、status_code,db有db.statement,mq有mq.broker、mq.topic,部分标签支持搜索(searchableTracesTags);
- startTime/endTime:span的开始时间和结束时间,注意这里都是客户端时间;
- componentId:组件id,官方支持的组件见ComponentsDefine,如11-Feign、14-SpringMVC;
AbstractTracingSpan#start:记录开始时间。

AbstractTracingSpan#finish:记录结束时间,并将span加入segment。

3、Span类型
span有三种类型:Local/Entry/Exist。
LocalSpan
一个普通的span,线程内部操作。
比如:
- 自定义LocalSpan,apm-toolkit-trace提供的Trace注解;
- 跨线程的Runnable是LocalSpan,apm-toolkit-trace提供了TraceCrossThread注解;
- SpringSchedule是LocalSpan;

StackBasedTracingSpan
Entry和Exist类型Span都继承StackBasedTracingSpan,特点是基于栈的数据结构可以多次start和finish。
StackBasedTracingSpan解决一次用户代码调用经过多个skywalking插件的问题,比如一个普通springboot应用,入口流量会经过tomcat和springmvc插件,但是只需要记录一个EntrySpan。
StackBasedTracingSpan持有stackDepth代表当前栈的深度,每次start栈深度+1,每次finish栈深度-1。
StackBasedTracingSpan复写了父类AbstractTracingSpan的finish方法,当且仅当span最后一次finish后,即最后一个插件执行完成后,才真正执行finish,记录span结束时间并加入segment。

EntrySpan
EntrySpan一般是流量入口,一个Segment(线程)中最多只有一个EntrySpan。
EntrySpan维护了一个currentMaxDepth,用于记录到达的最大深度。
EntrySpan#start:
首次start记录startTime,每次调用start都会清空一些span的属性,如componentId(Tomcat是1,SpringMVC是14)。


EntrySpan#tag:
以tag为例,只有最后一次调用Span#start的业务才能记录tag数据。

如Tomcat插件+SpringMVC插件:
- Tomcat:请求先经过Tomcat插件,创建EntrySpan,记录开始和结束时间;
- SpringMVC:在经过Tomcat之后,经过SpringMVC插件,这里不会再创建新EntrySpan,而是清空并覆盖Tomcat记录的数据(operationName、tag、componentId等);

ExitSpan
ExitSpan一般是流量出口,比如一次rpc调用、db查询。
ExistSpan#start:首次开启ExistSpan,记录开始时间。

ExitSpan#tag:只有首次开启ExistSpan的业务能记录tag、componentId等信息。

举例,如RestTemplate使用OkHttp客户端,对于用户来说只有一个get请求调用,但是实际经过了skywalking两个插件拦截:okhttp和RestTemplate。
由于先走RestTemplate插件,所以RestTemplate插件开启ExitSpan,记录开始时间、结束时间、tag、componentId等信息。
arduino
复制代码
RestTemplate template = new RestTemplate(); OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(); template.setRequestFactory(factory); template.getForEntity("http://service/api", String.class);
4、TraceSegmentRef
TraceSegment和Span可以通过TraceSegmentRef关联到上游segment的一个span。
TraceSegment会关联1个ref。
Span在rpc场景下只会关联1个ref,在批量消费场景下会关联n个ref。
通过TraceSegmentRef将不同进程线程之间的Segment/Span进行连接。
跨进程,通过ContextCarrier构造。

最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



