废话不多说,咱们直接上代码讲解
/**
* SkyWalking的traceId生成策略
* traceId 是用于唯一标识一个跟踪操作(trace)的标识符
*/
public class SkyWalkingTraceIdGenerator {
// 生成一个唯一的进程ID,使用UUID去除横杠
private static final String PROCESS_ID = UUID.randomUUID().toString().replaceAll("-", "");
/*
* 使用ThreadLocal保持每个线程的IDContext
* 在每个线程首次访问 THREAD_ID_SEQUENCE 时,会为该线程创建一个新的 IDContext 实例
* 并且这个实例会一直附着在这个线程上,供线程在后续的操作中使用
* 这是为了确保每个线程都有自己独立的 IDContext 实例,以避免线程之间的干扰
* 创建一个 ThreadLocal 对象,并通过提供的 Supplier 实例在每个线程首次访问该 ThreadLocal 对象时获取初始值
* 在这里,初始值就是由()-> new IDContext(System.currentTimeMillis(),(short) 0) 提供的,即每个线程都会获取一个新的IDContext实例
*/
private static final ThreadLocal<IDContext> THREAD_ID_SEQUENCE = ThreadLocal.withInitial(
//(short) 0 表示线程内序列的起始值,即 IDContext 类中的 threadSeq 字段的初始值。在这里,它将线程内序列的起始值设置为0
() -> new IDContext(System.currentTimeMillis(), (short) 0));
private SkyWalkingTraceIdGenerator() {
}
/**
* 生成一个新的ID,由三个部分组成。
* 第一个部分表示应用程序实例ID。
* 第二个部分表示线程ID。
* 第三个部分也有两个部分,1)时间戳(以毫秒为单位)2)线程内的序列,在0(包括)和9999(包括)之间。
*
* @return 用于表示跟踪或段的唯一ID
*/
public static String generate() {
return Joiner.on(".").join(
PROCESS_ID,
String.valueOf(Thread.currentThread().getId()),
String.valueOf(THREAD_ID_SEQUENCE.get().nextSeq())
);
}
// IDContext类用于保持线程特定的上下文,其中包含了跟踪 ID 生成所需的信息
private static class IDContext {
private static final int MAX_SEQ = 10_000;
//记录上一次的时间戳
private long lastTimestamp;
//记录线程内的序列
private short threadSeq;
// 记录上一次时间回移的时间戳,仅用于考虑由运维或操作系统引起的时间回移。
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) {
if (lastShiftTimestamp != currentTimeMillis) {
lastShiftValue++;
lastShiftTimestamp = currentTimeMillis;
}
return lastShiftValue;
} else {
lastTimestamp = currentTimeMillis;
return lastTimestamp;
}
}
private short nextThreadSeq() {
if (threadSeq == MAX_SEQ) {
threadSeq = 0;
}
return threadSeq++;
}
}
}
这是 IDContext 类中的几个关键方法的代码块的解释:
在正常情况下,timestamp() 方法确实返回最新的时间戳,因为在正常情况下,当前时间戳大于等于上一次记录的时间戳。
但是,当系统检测到时间回移时,它会调整时间戳,避免出现由于时间回移导致的不连续性。lastShiftValue 在这里的作用是记录时间回移的数量,而不是直接返回经过调整的时间戳。
为什么要这样设计呢?考虑以下情况: