源码角度了解Skywalking之Trace信息的生成
TraceId是分布式链路的一个信息,可以通过它定位一条链路
TraceId的生成
Skwalking的TraceId的生成是通过GlobalIdGenerator的generate()方法来生成的,
第一部分:具体是应用程序实例 ID
第二部分:线程 ID
第三部分:时间戳*10000+当前线程中的 seq,seq的值介于 0(包含)和 9999(包含)之间
三部分通过.来分隔开
我们知道SKywalking启动的是拦截实例方法的时候涉及到了InstMethodsInterWithOverrideArgs兰姐器和InstMethodsInter兰姐器,兰姐器的拦截方法中调用环绕兰姐器的beforeMethod()方法,这个方法中调用了ContextManager的createSpan()方法,afterMethod()方法中调用ContextManager的stopSpan()方法,我们看一下ContextManager这个类中涉及到的方法
ContextManager
ContextManager虽然也实现了BootService接口,但BootService的相关方法实现为空,ContextManager创建span的方法有三个,分别是createEntrySpan()方法、createLocalSpan()方法和createExitSpan()方法,EntrySpan是进入这个服务的时候创建的Span,比如消息队列的消费者入口,LocalSpan是本地方法调用的时候创建的Span,ExitSpan是离开这个服务创建的Span,比如发起远程调用的时候或者消息队列生产消息的时候
ContextManager的createEntrySpan()方法
ContextManager的createEntrySpan()方法
public static AbstractSpan createEntrySpan(String operationName, ContextCarrier carrier) {
AbstractSpan span;
AbstractTracerContext context;
operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);
if (carrier != null && carrier.isValid()) {
SamplingService samplingService = ServiceManager.INSTANCE.findService(SamplingService.class);
samplingService.forceSampled();
context = getOrCreate(operationName, true);
span = context.createEntrySpan(operationName);
context.extract(carrier);
} else {
context = getOrCreate(operationName, false);
span = context.createEntrySpan(operationName);
}
return span;
}
- 如果有上游服务,查找SamplingService实例调用forceSampled()方法强行采样
- 获取当前线程对应的TracingContext
- 调用TracingContext的createEntrySpan()方法创建EntrySpan,ActiveSpanStack栈存储了活动的span,如果这个栈中有span就放入栈中
- 提取上游的的Trace信息
- 如果没有上游服务就创建EntrySpan就可以了。
ContextManager的stopSpan()方法中关闭Span,调用ThreadLocal的remove()方法清除ThreadLocal防止内存泄露
TracingContext
TracingContext的createEntrySpan()方法
public AbstractSpan createEntrySpan(final String operationName) {
if (isLimitMechanismWorking()) {
NoopSpan span = new NoopSpan();
return push(span);
}
AbstractSpan entrySpan;
final AbstractSpan parentSpan = peek();
final int parentSpanId = parentSpan == null ? -1 : parentSpan.getSpanId();
if (parentSpan != null && parentSpan.isEntry()) {
entrySpan = (AbstractTracingSpan)DictionaryManager.findEndpointSection()
.findOnly(segment.getServiceId(), operationName)
.doInCondition(new PossibleFound.FoundAndObtain() {
@Override public Object doProcess(int operationId) {
return parentSpan.setOperationId(operationId);
}
}, new PossibleFound.NotFoundAndObtain() {
@Override public Object doProcess() {
return parentSpan.setOperationName(operationName);
}
});
return entrySpan.start();
} else {
entrySpan = (AbstractTracingSpan)DictionaryManager.findEndpointSection()
.findOnly(segment.getServiceId(), operationName)
.doInCondition(new PossibleFound.FoundAndObtain() {
@Override public Object doProcess(int operationId) {
return new EntrySpan(spanIdGenerator++, parentSpanId, operationId);
}
}, new PossibleFound.NotFoundAndObtain() {
@Override public Object doProcess() {
return new EntrySpan(spanIdGenerator++, parentSpanId, operationName);
}
});
entrySpan.start();
return push(entrySpan);
}
}
我们分析一下这一块的逻辑
- 调用isLimitMechanismWorking()方法判断是否超过最大的span数,默认是300,如果超过了最大数,创建NoopSpan对象,放入栈中返回
- 如果没有超过最大数,获取栈顶的span,也就是当前的span
- 如果父span不存在,创建EntrySpan对象,调用EntrySpan.start()开启span,start()方法其实就是设置startTime属性值为当前时间
- 如果父span存在就创建EntrySpan对象,开启span,然后压入栈中。
具体我们举个实例,看看这个三种类型的span是如何使用的
服务A
public void a() {
b();
c();
d.d();
}
服务A有个a()方法,a()方法中依次调用了服务自己的b()方法、c()方法,和服务d的d()方法,这里的栈操作具体为:
- 请求经过tomcat插件创建EntrySpan入栈,调用start()开启span
- 调用b()方法的时候创建 LocalSpan对象入栈,调用start()开启span,a()方法结束的时候出栈
- 调用c()方法的时候同样创建 LocalSpan对象入栈,调用start()开启span,b()方法结束的时候出栈
- 调用d()方法的时候创建ExitSpan对象入栈,服务b调用结束的时候ExitSpan出栈
- 接着a()结束的时候EntrySpan出栈。
总结
这篇文章我们主要讲了Trace在一个请求过来的时候是怎么创建的,span的类型有三种,EntrySpan是进入这个服务的时候创建的Span,LocalSpan是本地方法调用的时候创建的Span,ExitSpan是离开这个服务创建的Span,深入分析了ContextManager这个类,这个类主要完成的是当前线程和TracingContext的绑定,TracingContext这个类就是创建span的类了。
❤️ 感谢大家
如果你觉得这篇内容对你挺有有帮助的话:
- 欢迎关注我❤️,点赞 标签:调用,span,Trace,创建,operationName,EntrySpan,源码,Skywalking,方法 From: https://blog.51cto.com/u_15460453/5731520