skywalking的agent如何创建span(二)

  • 在tomcat插件中的beforeMethod方法中可以看到ContextManager.createEntrySpan。查看源码,重要的有两部分,第一部分为创建一个AbstractTracerContext,通过一步一步调试可以看到是创建了一个TracingContext(实现类),第二部分为创建EntrySpan。
private static ThreadLocal<AbstractTracerContext> CONTEXT = new ThreadLocal<AbstractTracerContext>();
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;
    }


private static AbstractTracerContext getOrCreate(String operationName, boolean forceSampling) {
    AbstractTracerContext context = CONTEXT.get();
    if (context == null) {
        if (StringUtil.isEmpty(operationName)) {
            if (LOGGER.isDebugEnable()) {
                LOGGER.debug("No operation name, ignore this trace.");
            }
            context = new IgnoredTracerContext();
        } else {
            if (EXTEND_SERVICE == null) {
                EXTEND_SERVICE = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class);
            }
            context = EXTEND_SERVICE.createTraceContext(operationName, forceSampling);

        }
        CONTEXT.set(context);
    }
    return context;
}

步骤一种创建的context会被存储到 ThreadLocal中,ThreadLocal保证了每个线程中的数据是独立的

在TracingContext中定义了TraceSegment,在TraceSegment中定义了一个private List<AbstractTracingSpan> spans,用于存储span信息。

/**
     * Initialize all fields with default value.
     */
    TracingContext(String firstOPName, SpanLimitWatcher spanLimitWatcher) {
        this.segment = new TraceSegment();
        this.spanIdGenerator = 0;
        isRunningInAsyncMode = false;
        createTime = System.currentTimeMillis();
        running = true;

        // profiling status
        if (PROFILE_TASK_EXECUTION_SERVICE == null) {
            PROFILE_TASK_EXECUTION_SERVICE = ServiceManager.INSTANCE.findService(ProfileTaskExecutionService.class);
        }
        this.profileStatus = PROFILE_TASK_EXECUTION_SERVICE.addProfiling(
            this, segment.getTraceSegmentId(), firstOPName);

        this.correlationContext = new CorrelationContext();
        this.extensionContext = new ExtensionContext();
        this.spanLimitWatcher = spanLimitWatcher;
    }
private List<AbstractTracingSpan> spans;


 public TraceSegment() {
        this.traceSegmentId = GlobalIdGenerator.generate();
        this.spans = new LinkedList<>();
        this.relatedGlobalTraceId = new NewDistributedTraceId();
        this.createTime = System.currentTimeMillis();

        //收集线程信息
        threadID = Thread.currentThread().getId();
        threadName = Thread.currentThread().getName();
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        threadGroup = (group == null ? null : group.getName());
    }

 

在步骤二中创建span,该span会被存储到activeSpanStack ,span信息会在afterMethod中获取出来,并对span信息补充。

 private LinkedList<AbstractSpan> activeSpanStack = new LinkedList<>();

/**
     * Add a new Span at the top of 'ActiveSpanStack'
     *
     * @param span the {@code span} to push
     */
    private AbstractSpan push(AbstractSpan span) {
        if (firstSpan == null) {
            firstSpan = span;
        }
        activeSpanStack.addLast(span);
        this.extensionContext.handle(span);
        return span;
    }


afterMethod源码可以看到会获取activeSpan,也就是activeSpanStack 中的span信息,在afterMethod中完成对span信息的补充后,会调用stopSpan


 @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                    Object ret) throws Throwable {
        Request request = (Request) allArguments[0];
        HttpServletResponse response = (HttpServletResponse) allArguments[1];

        AbstractSpan span = ContextManager.activeSpan();

        //设置所处的类信息
        Tags.LOCATION.set(span, objInst.getClass().getName());
if (IS_SERVLET_GET_STATUS_METHOD_EXIST && response.getStatus() >= 400) {
            span.errorOccurred();
            Tags.STATUS_CODE.set(span, Integer.toString(response.getStatus()));
        }
        // Active HTTP parameter collection automatically in the profiling context.
        if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) {
            collectHttpParam(request, span);
        }
        ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
        ContextManager.stopSpan();
        return ret;
    }

 

继续跟踪stopSpan,可以看到在执行玩stopSpan后,会把对应的CONTEXT(也就是之前说的TracingContext)从ThreadLocal中移除
private static void stopSpan(AbstractSpan span, final AbstractTracerContext context) {
        if (context.stopSpan(span)) {
            CONTEXT.remove();
            RUNTIME_CONTEXT.remove();
        }
    }

继续追踪stopSpan到finish方法,可以看到第一部分的红色代码,判断activeSpanStack是否还有span信息,该结果作为判断是否调用notifyFinish实现数据向collector
发送的条件之一。这么做的意义在哪里呢?可以想象一下,Tomcat作为入口,探针为其创建了EntrySpan,在tomcat应用中可能会通过jdbc调用数据库(ExitSpan)。这就像括号匹配一样
"{[()]}"每个span都有beforeMethod和AfterMethod,只有当所有的afterMethod执行完后才能组建成一个完整的TraceSegment(数据的发送是以每个TraceSegment为单位的)。
/**
     * Finish this context, and notify all {@link TracingContextListener}s, managed by {@link
     * TracingContext.ListenerManager} and {@link TracingContext.TracingThreadListenerManager}
     */
    private void finish() {
        if (isRunningInAsyncMode) {
            asyncFinishLock.lock();
        }
        try {
            boolean isFinishedInMainThread = activeSpanStack.isEmpty() && running;
            if (isFinishedInMainThread) {
                /*
                 * Notify after tracing finished in the main thread.
                 */
                TracingThreadListenerManager.notifyFinish(this);
            }

            if (isFinishedInMainThread && (!isRunningInAsyncMode || asyncSpanCounter == 0)) {
                TraceSegment finishedSegment = segment.finish(isLimitMechanismWorking());
                TracingContext.ListenerManager.notifyFinish(finishedSegment);
                running = false;
            }
        } finally {
            if (isRunningInAsyncMode) {
                asyncFinishLock.unlock();
            }
        }
    }

 

追踪notifyFinish方法一直到Channels类的save方法,数据会存放在本地缓存bufferChannels中。

public class Channels<T> {
    private final QueueBuffer<T>[] bufferChannels;
   
  ......
  ......
  
    public boolean save(T data) {
        int index = dataPartitioner.partition(bufferChannels.length, data);
        int retryCountDown = 1;
        if (BufferStrategy.IF_POSSIBLE.equals(strategy)) {
            int maxRetryCount = dataPartitioner.maxRetryCount();
            if (maxRetryCount > 1) {
                retryCountDown = maxRetryCount;
            }
        }
        for (; retryCountDown > 0; retryCountDown--) {
            if (bufferChannels[index].save(data)) {
                return true;
            }
        }
        return false;
    }

   ......
  ......
}

 

该缓存会通过另外一个线程实现数据的发送
 @Override
    public void run() {
        running = true;

        final List consumeList = new ArrayList(2000);
        while (running) {
            boolean hasData = false;
            for (Group target : consumeTargets) {
                boolean consume = consume(target, consumeList);
                hasData = hasData || consume;
            }

            if (!hasData) {
                try {
                    Thread.sleep(consumeCycle);
                } catch (InterruptedException e) {
                }
            }
        }

        // consumer thread is going to stop
        // consume the last time
        for (Group target : consumeTargets) {
            consume(target, consumeList);

            target.consumer.onExit();
        }
    }

 



posted @ 2022-03-13 20:31  骑着蜗牛去救你  阅读(834)  评论(0编辑  收藏  举报