Skywalking 完整的链路

经过前面的介绍,我们知道了Skywalking上报的数据其实是Span,那么根据这些Span就可以将整条链路组装完成。接下来就看一下Skywalking是怎么组装的吧。

在组装之前,我们先看看每个零部件(Span)的结构吧。

public abstract class AbstractTracingSpan implements AbstractSpan {
    protected int spanId;  // 每个Span在Segment中的序号
    protected int parentSpanId; // 父级Span的序号
    protected List tags;
    protected String operationName; // 这个Span的标示,比如一个Http请求入口,这个就是url
    protected int operationId;
    protected SpanLayer layer;
    protected boolean isInAsyncMode = false;
    protected volatile AbstractTracerContext context;
    protected long startTime; // Span的开始时间
    protected long endTime; // Span的结束时间
    protected boolean errorOccurred = false;
    protected int componentId = 0;
    protected String componentName;
    protected List logs;
    protected List refs; // 上一个Segment的引用
}

这个是 Skywalking 中AbstractTracingSpan的定义,这里对接下来我们需要的几个属性进行了注释。

通过上面的定义我们知道了几点:

  1. 在Segment中每个Span都有一个SpanId,这些SpanId通过parentSpanId可以在Segment范围内形成一个具有层级的树结构
Skywalking 完整的链路_第1张图片
Segment中的Span结构
  1. 不同Segemnt通过List refs 串联起来。
Skywalking 完整的链路_第2张图片
多个Segement连接

所以只有EntrySpan中才会有refs

按照上面所说的,每个EntrySpan除了链路最开始的那个,应该只有一个TraceSegmentRef,为什么AbstractTracingSpan中却定义的却是一个List呢?也就是为啥会有多个爸爸呢?

是的,对于大部分的请求(比如一次RPC)EntrySpan只有一个爸爸,但是不排除一些EntrySpan搞事情,竟然出去认了干爹。

好了,不扯淡了,那么什么情况下会出现一个EntrySpan会有多个TraceSegmentRef呢?

在一些批处理或使用了消息队列的场景下就会出现这种情况,我们就拿消息队列来说明吧。

Skywalking 完整的链路_第3张图片
消息队列

消费者批量消费消息,而这些消息分别来自不同的生产者,这种情况下,消费者这个EntrySpan就会是多个TraceSegmentRef

那么在有消息队列这种场景下,怎么将链路串联呢?可以看到,这里其实已经是条链路了,所以如果Consumer上报数据,能够将多条Traces数据存储下来的,那么每条Trace的数据都能完成的保存。

但是,就我看Skywalking oap-server(agent会将数据上报给oap-server )的源代码,在SegmentParseV2这个类中处理上报数据时

SegmentParseV2

public boolean parse(BufferData bufferData, SegmentSource source) {
  createSpanListeners();
  UpstreamSegment upstreamSegment = bufferData.getMessageType();
  UpstreamSegment upstreamSegment = bufferData.getMessageType();
  List traceIds = upstreamSegment.getGlobalTraceIdsList();
  if (bufferData.getV2Segment() == null) {
    bufferData.setV2Segment(parseBinarySegment(upstreamSegment));
  }
  SegmentObject segmentObject = bufferData.getV2Segment();
  SegmentDecorator segmentDecorator = new SegmentDecorator(segmentObject);
  if (!preBuild(traceIds, segmentDecorator)) {
    ...
  }
}


private boolean preBuild(List traceIds, SegmentDecorator segmentDecorator) {
  ...

   for (UniqueId uniqueId : traceIds) {
     notifyGlobalsListener(uniqueId);
  }
}


private void notifyGlobalsListener(UniqueId uniqueId) {
    spanListeners.forEach(listener -> {
        if (listener.containsPoint(SpanListener.Point.TraceIds)) {
            ((GlobalTraceIdsListener)listener).parseGlobalTraceId(uniqueId, segmentCoreInfo);
        }
    });
}

SegmentSpanListener

public void parseGlobalTraceId(UniqueId uniqueId, SegmentCoreInfo segmentCoreInfo) {
    ...
    segment.setTraceId(traceIdBuilder.toString());
}

可以看到对于每一个上报的UpstreamSegment,都会创建一个SegmentSpanListener,但是在SegmentSpanListenersegment属性却只有一个,所以Agent上报了多条TraceId,但是到了SegmentSpanListener只保存了一条TraceId

所以在这种情况,消费者每次上报的Segment涉及到的多条Trace只有一条是完整的,其他的Trace到生产者这条链路就断掉了。

可能对于消息队列这种场景我的理解还不到位,如有不对的,请各位指正。

上面说的只是跨进程的情况,还有一种跨线程的情况需要说明一下。

由于Skywalking Agent 使用的是ThreadLocal保存的Segment数据,所以跨线程执行时,其实就是一个新的Segment了,只不过这个跨线程的Segement的入口不是EntrySpan而是LocalSpan

你可能感兴趣的:(Skywalking 完整的链路)