源码角度了解Skywalking之Trace信息的生成

本文从源码角度解析Skywalking的TraceId生成机制,包括应用程序实例ID、线程ID和时间戳的组合方式。详细介绍了ContextManager和TracingContext在创建EntrySpan、LocalSpan和ExitSpan过程中的作用,以及在不同服务调用场景下的应用。

源码角度了解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的类了。
 

SkyWalking 的 `traceId` 默认是由其内部的 ID 生成机制自动生成的,通常是一个 18 字节长度的字符串,包含时间戳、实例 ID 和随机数等信息。官方文档中提到,SkyWalking 并不直接支持通过配置文件来自定义 `traceId` 的生成规则[^1]。 然而,在实际业务场景中,确实存在需要统一生成特定格式 `traceId` 的需求,例如为了与企业内部的日志系统或监控平台保持一致性。在这种情况下,可以通过实现 SkyWalking 提供的 SPI(Service Provider Interface)接口来扩展其功能,从而达到自定义 `traceId` 生成逻辑的目的[^1]。 要自定义 `traceId` 的生成方式,需要遵循以下步骤: - 创建一个类并实现 `org.apache.skywalking.apm.dependencies.org.slf4j.spi.MDCAdapter` 接口或者更准确地说是 `org.apache.skywalking.apm.toolkit.spi.TraceIdGenerator` 接口(如果可用),该接口允许你提供自己的 `traceId` 生成策略。 - 在资源目录下创建 `META-INF/services/org.apache.skywalking.apm.toolkit.spi.TraceIdGenerator` 文件,并在其中声明你的实现类名。 - 编写自定义的 `traceId` 生成逻辑,确保它能够满足业务需求并且在整个分布式系统中保持唯一性。 - 将自定义的 JAR 包放置于应用的 classpath 中,这样 SkyWalking 就可以加载并使用新的 `traceId` 生成器了。 下面是一个简化的示例代码,展示如何开始编写一个自定义的 `traceId` 生成器: ```java package com.example; import org.apache.skywalking.apm.toolkit.spi.TraceIdGenerator; public class CustomTraceIdGenerator implements TraceIdGenerator { @Override public String generate() { // 实现自定义的 traceId 生成逻辑 return "custom-" + System.currentTimeMillis() + "-0001"; // 示例格式 } } ``` 然后,在 `META-INF/services/org.apache.skywalking.apm.toolkit.spi.TraceIdGenerator` 文件中添加如下内容: ``` com.example.CustomTraceIdGenerator ``` 需要注意的是,由于 SkyWalking 的版本更新可能导致 API 变化,因此具体的接口名称和方法可能有所不同。务必参考当前使用的 SkyWalking 版本文档以及源码中的 SPI 定义来确定正确的接口路径和使用方式。 此外,对于 Spring Cloud Gateway 等特定框架集成的情况下,也可以通过拦截器或过滤器的方式,在请求进入服务时注入自定义的 `traceId` 到上下文中,以保证整个链路追踪的一致性[^2]。 总之,尽管 SkyWalking 不直接支持配置式的 `traceId` 自定义,但借助其提供的扩展点和服务治理能力,开发者仍然可以灵活地控制 `traceId` 的生成过程,以适应不同的业务场景需求。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值