源码角度了解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;
    }
  1. 如果有上游服务,查找SamplingService实例调用forceSampled()方法强行采样
  2. 获取当前线程对应的TracingContext
  3. 调用TracingContext的createEntrySpan()方法创建EntrySpan,ActiveSpanStack栈存储了活动的span,如果这个栈中有span就放入栈中
  4. 提取上游的的Trace信息
  5. 如果没有上游服务就创建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);
        }
    }

我们分析一下这一块的逻辑

  1. 调用isLimitMechanismWorking()方法判断是否超过最大的span数,默认是300,如果超过了最大数,创建NoopSpan对象,放入栈中返回
  2. 如果没有超过最大数,获取栈顶的span,也就是当前的span
  3. 如果父span不存在,创建EntrySpan对象,调用EntrySpan.start()开启span,start()方法其实就是设置startTime属性值为当前时间
  4. 如果父span存在就创建EntrySpan对象,开启span,然后压入栈中。

具体我们举个实例,看看这个三种类型的span是如何使用的

服务A

public void a() {
   b();
   c();
   d.d();
}

服务A有个a()方法,a()方法中依次调用了服务自己的b()方法、c()方法,和服务d的d()方法,这里的栈操作具体为:

  1. 请求经过tomcat插件创建EntrySpan入栈,调用start()开启span
  2. 调用b()方法的时候创建 LocalSpan对象入栈,调用start()开启span,a()方法结束的时候出栈
  3. 调用c()方法的时候同样创建 LocalSpan对象入栈,调用start()开启span,b()方法结束的时候出栈
  4. 调用d()方法的时候创建ExitSpan对象入栈,服务b调用结束的时候ExitSpan出栈
  5. 接着a()结束的时候EntrySpan出栈。

总结

这篇文章我们主要讲了Trace在一个请求过来的时候是怎么创建的,span的类型有三种,EntrySpan是进入这个服务的时候创建的Span,LocalSpan是本地方法调用的时候创建的Span,ExitSpan是离开这个服务创建的Span,深入分析了ContextManager这个类,这个类主要完成的是当前线程和TracingContext的绑定,TracingContext这个类就是创建span的类了。

❤️ 感谢大家

如果你觉得这篇内容对你挺有有帮助的话:

  1. 欢迎关注我❤️,点赞👍🏻,评论🤤,转发🙏
  2. 有不当之处欢迎批评指正。