Trace ID

来自ling
跳转至: 导航搜索

在分布式链路追踪系统中,用户请求的处理过程会形成一条 Trace 。Trace ID 作为 Trace 数据的唯一标识,在面对海量请求的时候,需要保证其唯一性。与此同时,还要保证生成 Trace ID 不会带来过多开销,所以在业务场景中依赖数据库(自增键或是类似 Meituan-Dianping/Leaf 的 ID 生成方式)都不适合 Trace 的场景。

这种要求快速、高性能生成唯一 ID 的需求场景,一般会将 snowflake 算法与实际的场景集合进行改造。

snowflake 算法是 Twitter 开源的分布式 ID 生成算法 。snowflake 算法的核心思想是将一个 ID(long类型)的 64 个 bit 进行切分,其中使用 41 个 bit 作为毫秒数,10 个 bit 作为机器的 ID( 5 个 bit 记录数据中心的 ID,5 个 bit 记录机器的 ID ),12 bit 作为毫秒内的自增 ID,还有一个 bit 位永远是 0。snowflake 算法生成的 ID 结构如下图所示:


snowflake 算法的好处是 ID 可以直接靠算法在内存中产生,内存内的锁控制并发,不需依赖 MySQL 这样的外部依赖,无维护成本。缺点就是每个机器节点在每毫秒内只可以产生 4096 个 ID,超出这个范围就会溢出。另外,如果机器回拨了时间,就会生成重复的 ID。

ID 类是 SkyWalking 中对全局唯一标识的抽象,其生成策略与 snowflake 算法类似。SkyWalking ID 由三个 long 类型的字段(part1、part2、part3)构成,分别记录了 ServiceInstanceId、Thread ID 和 Context 生成序列。Context 生成序列的格式是:

复制代码

${时间戳} * 10000 + 线程自增序列([0, 9999])

ID 对象序列化之后的格式是将 part1、part2、part3 三部分用“.”分割连接起来 :

复制代码

${ServiceInstanceId}.${Thread ID}.(${时间戳} * 10000 + 线程自增序列([0, 9999]))

GlobalIdGenerator 是 Agent 中用来生成全局唯一 ID 的基础工具类,在 generate() 方法中的实现如下:

复制代码

public static ID generate() {
    // THREAD_ID_SEQUENCE是 ThreadLocal<IDContext>类型,即每个线程
    // 维护一个 IDContext对象
    IDContext context = THREAD_ID_SEQUENCE.get(); 
    return new ID(SERVICE_INSTANCE_ID, // service_intance_id
        Thread.currentThread().getId(), // 当前线程的ID
        context.nextSeq() // 线程内生成的序列号
    );
}

IDContext.nextSeq() 方法的实现如下,其中 timestamp() 方法在返回时间戳的时候,会处理时间回拨的场景(使用 Random 随机生成一个时间戳),nextThreadSeq() 方法的返回值在 [0 , 9999] 这个范围内循环:

复制代码

private long nextSeq() {
    return timestamp() * 10000 + nextThreadSeq();
}

GlobalIdGenerator 不仅用于生成 Trace ID ,其他需要唯一 ID 的地方也会通过其 nextSeq() 方法生成。