本文基于 SkyWalking-Java-agent 8.15.0 版本
推荐文档
- Google的dapper论文:static.googleusercontent.com/media/resea…
- opentracing标准:github.com/opentracing…
基本概念
Trace
概念:表示一整条链路(跨线程、跨进程的所有 Segment 的集合)
Trace 不是一个具体的数据模型,而是多个 Segment(SkyWalking中定义为 TraceSegment)串起来表示的逻辑对象
TraceSegment
基本概念
分布式链路追踪的节点。一个TraceSegment表示当前线程的一个节点。分布式链路跟踪是跨多个进程、跨多个线程的,导致了一条分布式跟踪的链路就会有多个 TraceSegment 组成
属性
- String
traceSegmentId:链路节点的id,每个 segment 都有一个全局唯一的id - TraceSegmentRef
ref:segment的链路指针,指向当前segment的parent segment(上一级/父节点) - List<AbstractTracingSpan>
spans:已完成的具体操作 - DistributedTraceId
relatedGlobalTraceId:当前segment所在Trace的id - boolean
ignore= false:是否忽略 - boolean
isSizeLimited= false:是否有数量限制 finallongcreateTime:创建时间
构造器
/**
* Create a default/empty trace segment, with current time as start time, and generate a new segment id.
* 创建默认的trace segment,当前时间作为开始时间,并生成一个新的 traceSegmentId
*/
public TraceSegment() {
this.traceSegmentId = GlobalIdGenerator.generate();
this.spans = new LinkedList<>();
this.relatedGlobalTraceId = new NewDistributedTraceId();
this.createTime = System.currentTimeMillis();
}
全局id生产器
GlobalIdGenerator
属性:
// 应用实例id
private static final String PROCESS_ID = UUID.randomUUID().toString().replaceAll("-", "");
// 线程id序号
private static final ThreadLocal<IDContext> THREAD_ID_SEQUENCE = ThreadLocal.withInitial(
() -> new IDContext(System.currentTimeMillis(), (short) 0));
核心方法:
/**
* 一个新id有三部分构成
* 1、第一部分:应用实例 id
* 2、第二部分:线程id
* 3、第三部分:有两个部分构成
* 1)时间戳
* 2)当前线程里的序列号(0 - 9999)
*/
public static String generate() {
return StringUtil.join(
'.',
PROCESS_ID,
String.valueOf(Thread.currentThread().getId()),
String.valueOf(THREAD_ID_SEQUENCE.get().nextSeq())
);
}
IDContext:用于线程序号的生成
核心逻辑: timestamp() * 10000 + nextThreadSeq() 算法注意事项:考虑时间回拨
private static class IDContext {
// 上次生产 sequence 的时间戳
private long lastTimestamp;
// 线程的序列号
private short threadSeq;
// Just for considering time-shift-back only.
// 时间回拨
private long lastShiftTimestamp;
private int lastShiftValue;
private IDContext(long lastTimestamp, short threadSeq) {
this.lastTimestamp = lastTimestamp;
this.threadSeq = threadSeq;
}
private long nextSeq() {
return timestamp() * 10000 + nextThreadSeq();
}
private long timestamp() {
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis < lastTimestamp) { // 发生了时间回拨
// Just for considering time-shift-back by Ops or OS. @hanahmily 's suggestion.
if (lastShiftTimestamp != currentTimeMillis) {
lastShiftValue++;
lastShiftTimestamp = currentTimeMillis;
}
return lastShiftValue;
} else { // 时间正常
lastTimestamp = currentTimeMillis;
return lastTimestamp;
}
}
private short nextThreadSeq() {
if (threadSeq == 10000) {
threadSeq = 0;
}
return threadSeq++;
}
}
分布式链路id
DistributedTraceId 表示一条分布式调用链。在一条链路中(Trace),无论请求分布于多少不同的进程中,这个 TraceId 都不会改变
public abstract class DistributedTraceId {
@Getter
private final String id;
}
TraceSegmentRef:用于指向当前 Segment 的 Parent Segment
Span
概念:表示具体的一个操作
AsyncSpan
- AbstractSpan
prepareForAsync():span在当前跟踪上下文结束,但当前span在调用asyncFinish前 仍然是活的 。在活跃期间,可以在任何线程中更改span的标签、日志和属性。这个方法在以下场景下必须被调用- 1、在原始线程中(跟踪上下文)
- 2、当前span处于活跃的
- AbstractSpan
asyncFinish():完成后通知 span
AbstractSpan
AbstractSpan 代表 span 的骨架,包含所有的公共方法。
/**
* The <code>AbstractSpan</code> represents the span's skeleton, which contains all open methods.
*/
public interface AbstractSpan extends AsyncSpan {
/**
* Set the component id, which defines in {@link ComponentsDefine}
* 指定当前 Span 表示的操作发生在哪个插件上
*
* @return the span for chaining.
*/
AbstractSpan setComponent(Component component);
/**
* 指定当前 Span 表示的操作所在的插件属于哪一种 skywalking 划分的类型
* @param layer
* @return
*/
AbstractSpan setLayer(SpanLayer layer);
/**
* Set a key:value tag on the Span.
*
* @return this Span instance, for chaining
* @deprecated use {@link #tag(AbstractTag, String)} in companion with {@link Tags#ofKey(String)} instead
*/
@Deprecated
AbstractSpan tag(String key, String value);
AbstractSpan tag(AbstractTag<?> tag, String value);
/**
* Record an exception event of the current walltime timestamp.
* walltime 挂钟时间,本地时间
* serverTime 服务器时间
*
* @param t any subclass of {@link Throwable}, which occurs in this span.
* @return the Span, for chaining
*/
AbstractSpan log(Throwable t);
AbstractSpan errorOccurred();
/**
* @return true if the actual span is an entry span.
*/
boolean isEntry();
/**
* @return true if the actual span is an exit span.
*/
boolean isExit();
/**
* Record an event at a specific timestamp.
*
* @param timestamp The explicit timestamp for the log record.
* @param event the events
* @return the Span, for chaining
*/
AbstractSpan log(long timestamp, Map<String, ?> event);
/**
* Sets the string name for the logical operation this span represents.
* 如果当前 Span 的操作是
* 一个 HTTP 请求,那么 operationName 就是请求的 URL
* 一个 SQL 请求,那么 operationName 就是 SQL 的类型
* 一个 Redis 请求,那么 operationName 就是 Redis 命令
*
* @return this Span instance, for chaining
*/
AbstractSpan setOperationName(String operationName);
/**
* Start a span.
*
* @return this Span instance, for chaining
*/
AbstractSpan start();
/**
* Get the id of span
*
* @return id value.
*/
int getSpanId();
String getOperationName();
/**
* Reference other trace segment.
*
* @param ref segment ref
*/
void ref(TraceSegmentRef ref);
AbstractSpan start(long startTime);
/**
* 什么叫 peer,就是对端地址
* 一个请求可能跨多个进程,操作多种中间件,那么每一次 RPC,对端的服务地址就是 remotePeer
* 每一次中间件的操作,中间件的地址就是 remotePeer
* @param remotePeer
* @return
*/
AbstractSpan setPeer(String remotePeer);
/**
* @return true if the span's owner(tracing context main thread) is been profiled.
*/
boolean isProfiling();
/**
* Should skip analysis in the backend.
*/
void skipAnalysis();
}
SpanLayer:span 分层,SkyWalking对不同数据源的分类
- DB(1):存储
- RPC_FRAMEWORK(2):rpc框架
- HTTP(3):http请求
- MQ(4):消息队列
- CACHE(5):缓存
OfficialComponent:SkyWalking 支持的官网组件
ComponentsDefine:定义了所有 SkyWalking 支持的 官网组件 清单列表
StackBasedTracingSpan
基于栈的 TracingSpan,
- int stackDepth:栈深度
- String peer:对端地址
LocalSpan
用于记录一个本地方法的调用,在一个Segment里,会有多个LocalSpan
public class LocalSpan extends AbstractTracingSpan {
public LocalSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) {
super(spanId, parentSpanId, operationName, owner);
}
@Override
public boolean isEntry() {
return false;
}
@Override
public boolean isExit() {
return false;
}
@Override
public AbstractSpan setPeer(String remotePeer) {
return this;
}
}
EntrySpan
- 在一个 TraceSegment 里面只能存在一个 EntrySpan
- 后面的插件复用前面插件创建的 EntrySpan 时会覆盖掉前面插件设置的 Span 信息
- EntrySpan 记录的信息永远是最靠近服务提供侧的信息
public class EntrySpan extends StackBasedTracingSpan {
private int currentMaxDepth; // 最大栈深
...
/**
* Set the {@link #startTime}, when the first start, which means the first service provided.
* EntrySpan 只会由第一个插件创建,但是后面的插件复用 EntrySpan 时都要来调用一次 start() 方法
* 因为每一个插件都以为自己是第一个创建 EntrySpan 的
*/
@Override
public EntrySpan start() {
if ((currentMaxDepth = ++stackDepth) == 1) {
super.start();
}
clearWhenRestart();
return this;
}
...
}
ExitSpan
区别就在于 EntrySpan 记录的是更靠近服务这一侧的信息。ExitSpan 记录的是更靠近消费这一侧的信息
- 所谓 ExitSpan 和 EntrySpan 一样采用复用的机制,前提是在插件嵌套的情况下
- 多个 ExitSpan 不存在嵌套关系,是平行存在的时候,是运行同时存在多个 ExitSpan
- 把 ExitSpan 简单理解为离开当前进程/线程的操作
- TraceSegment 里不一定非要有 ExitSpan
链路追踪上下文
TracingContext 管理
- 当前 Segment 和自己前后的 Segment 的引用 TraceSegmentRef
- 当前 Segment 内的所有 span