traceId:SkyWalking的traceId生成策略

废话不多说,咱们直接上代码讲解

/**
 * 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 类中的几个关键方法的代码块的解释:

  1. private IDContext(long lastTimestamp, short threadSeq) { ... }: 构造函数,用于初始化 IDContext 实例的初始状态。
    ○ this.lastTimestamp = lastTimestamp;: 将传入的 lastTimestamp 参数赋值给实例变量 lastTimestamp,表示上一次记录的时间戳。
    ○ this.threadSeq = threadSeq;: 将传入的 threadSeq 参数赋值给实例变量 threadSeq,表示线程内序列。
  2. private long nextSeq() { ... }: 生成下一个序列,由时间戳和线程内序列组成。
    ○ return timestamp() * 10000 + nextThreadSeq();: 调用 timestamp() 方法获取当前时间戳,乘以 10000,然后加上调用 nextThreadSeq() 方法获取的下一个线程内序列。
  3. private long timestamp() { ... }: 获取当前时间戳,考虑了时间回移的情况。
    ○ long currentTimeMillis = System.currentTimeMillis();: 获取当前时间戳。
    ○ if (currentTimeMillis < lastTimestamp) { ... }: 如果当前时间小于上一次记录的时间戳,表示发生了时间回移。
    ■ if (lastShiftTimestamp != currentTimeMillis) { ... }: 如果上一次时间回移的时间戳不等于当前时间戳,表示这是第一次发现时间回移。
    ● lastShiftValue++;: 时间回移值加1,用于在 timestamp() 方法中计算回移后的时间。
    ● lastShiftTimestamp = currentTimeMillis;: 记录时间回移的时间戳。
    ■ return lastShiftValue;: 返回回移后的时间。
    ○ else { ... }: 如果当前时间大于等于上一次记录的时间戳,表示正常情况。
    ■ lastTimestamp = currentTimeMillis;: 更新上一次记录的时间戳。
    ■ return lastTimestamp;: 返回当前时间戳。
  4. private short nextThreadSeq() { ... }: 获取下一个线程内序列,注意在达到最大值后会重新从0开始。
    ○ if (threadSeq == MAX_SEQ) { ... }: 如果线程内序列达到最大值,重置为0。
    ■ threadSeq = 0;: 重置线程内序列。
    ○ return threadSeq++;: 返回当前线程内序列值,并将其递增。

在正常情况下,timestamp() 方法确实返回最新的时间戳,因为在正常情况下,当前时间戳大于等于上一次记录的时间戳。

但是,当系统检测到时间回移时,它会调整时间戳,避免出现由于时间回移导致的不连续性。lastShiftValue 在这里的作用是记录时间回移的数量,而不是直接返回经过调整的时间戳。

为什么要这样设计呢?考虑以下情况:

  1. 时间回移的可能性: 操作系统或运维可能会引起时间回移,导致当前时间小于上一次记录的时间。在这种情况下,返回 lastShiftValue 允许应用程序意识到发生了时间回移,而不是简单地返回一个调整后的时间戳。
  2. 连续性和递增性: 返回 lastShiftValue 可以确保时间戳的连续性和递增性。在时间回移的情况下,返回 lastShiftValue 提供了一种方式来追踪这种变化。这有助于保持生成的 ID 在整个系统中的一致性,避免由于时间回移引起的问题。
    总体来说,这个设计是为了在系统检测到时间回移时提供一种反映这一情况的机制,并确保生成的时间序列仍然是有序和递增的。
相关推荐
cdut_suye6 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
苹果醋318 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行19 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园23 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
wm10431 小时前
java web springboot
java·spring boot·后端
smile-yan1 小时前
Provides transitive vulnerable dependency maven 提示依赖存在漏洞问题的解决方法
java·maven
老马啸西风1 小时前
NLP 中文拼写检测纠正论文-01-介绍了SIGHAN 2015 包括任务描述,数据准备, 绩效指标和评估结果
java
Earnest~1 小时前
Maven极简安装&配置-241223
java·maven
皮蛋很白1 小时前
Maven 环境变量 MAVEN_HOME 和 M2_HOME 区别以及 IDEA 修改 Maven repository 路径全局
java·maven·intellij-idea
青年有志1 小时前
JavaWeb(一) | 基本概念(web服务器、Tomcat、HTTP、Maven)、Servlet 简介
java·web