Micrometer 系列【42】链路追踪:Span 体系 | 核心 API

文章目录

  • [1. 前言](#1. 前言)
  • [2. SpanCustomizer:轻量链路定制器](#2. SpanCustomizer:轻量链路定制器)
    • [2.1 核心定义](#2.1 核心定义)
    • [2.2 常量定义](#2.2 常量定义)
    • [2.3 方法定义](#2.3 方法定义)
      • [2.3.1 修改链路名称](#2.3.1 修改链路名称)
      • [2.3.2 添加标签](#2.3.2 添加标签)
      • [2.3.3 链路事件记录](#2.3.3 链路事件记录)
    • [2.4 与 Span 的核心区别](#2.4 与 Span 的核心区别)
  • [2. Span:顶层核心](#2. Span:顶层核心)
    • [2.1 核心定义](#2.1 核心定义)
    • [2.2 方法定义](#2.2 方法定义)
    • [2.3 内部类/内部接口](#2.3 内部类/内部接口)
      • [2.3.1 枚举 Span.Kind(链路类型)](#2.3.1 枚举 Span.Kind(链路类型))
      • [2.3.2 接口 Span.Builder( Span 构建器)](#2.3.2 接口 Span.Builder( Span 构建器))
  • [3. OtelSpan (OpenTelemetry 桥接实现)](#3. OtelSpan (OpenTelemetry 桥接实现))
    • [3.1 核心定义](#3.1 核心定义)
    • [3.2 核心成员变量](#3.2 核心成员变量)
    • [3.3 核心常量](#3.3 核心常量)
    • [3.4 静态转换工具方法](#3.4 静态转换工具方法)
    • [3.5 方法定义](#3.5 方法定义)
      • [3.5.1 构造方法](#3.5.1 构造方法)
      • [3.5.2 状态与上下文](#3.5.2 状态与上下文)
      • [3.5.3 生命周期实现(重点特性)](#3.5.3 生命周期实现(重点特性))
      • [3.5.4 标签、事件、名称实现](#3.5.4 标签、事件、名称实现)
      • [3.5.5 异常埋点](#3.5.5 异常埋点)
      • [3.5.6 远端服务埋点](#3.5.6 远端服务埋点)
      • [3.5.7 内部辅助方法](#3.5.7 内部辅助方法)
      • [3.5.8 通用方法覆写](#3.5.8 通用方法覆写)

1. 前言

Micrometer Tracing 提供一套统一、抽象、可插拔 的链路追踪 API,底层同时兼容 BraveOpenTelemetry 两大主流追踪实现。

整个链路追踪体系中,Span 是最核心的工作单元,承担链路埋点、生命周期管理、标签事件记录、异常上报等核心能力。

本文一次性完整拆解三大核心源码组件:

  • Span:顶层核心接口,定义链路单元完整生命周期与能力
  • SpanCustomizer:轻量 Span 定制接口,无实例操作当前链路
  • OtelSpanOpenTelemetry 底层桥接实现类

2. SpanCustomizer:轻量链路定制器

2.1 核心定义

SpanCustomizer 当前线程活跃 Span 操作门面,不需要手动创建、启动、结束 Span只用来修改已经存在Span 信息

核心特性:

  • 作用域绑定:仅操作当前线程已存在的生效 Span
  • 链式调用:所有方法支持链式编程,代码简洁
  • 零侵入空实现:内置 NOOP 空实例,关闭追踪时无开销、无需业务判空
  • 能力精简:仅支持修改、追加数据,无法创建、启停、销毁 Span

2.2 常量定义

内置全局空实现实例,链路追踪关闭时自动使用,避免空指针与多余逻辑判断:

java 复制代码
SpanCustomizer NOOP = new SpanCustomizer() { ... };

2.3 方法定义

2.3.1 修改链路名称

更新当前作用域 Span 的操作名称,返回自身支持链式调用。

java 复制代码
    SpanCustomizer name(String name);

2.3.2 添加标签

java 复制代码
/**
 * 为 Span 设置标签。
 * @param key 标签键
 * @param value 标签值
 * @return 当前 {@link SpanCustomizer} 对象,支持链式调用
 */
SpanCustomizer tag(String key, String value);

内置默认重载方法,支持多数据类型自动转换:

  • tag(String key, long value):长整型标签
  • tag(String key, double value):浮点型标签
  • tag(String key, boolean value):布尔型标签

代码示例:

java 复制代码
// 获取当前上下文正在运行 Span 的定制器
// SpanCustomizer 仅用于修改已存在的Span,不能创建新Span
SpanCustomizer customizer = tracer.currentSpanCustomizer();
customizer
        // 修改链路Span名称
        .name("order:create")
        // 添加标签,用于链路检索、筛选维度
        .tag("order.id", "10001")
        // 添加业务标签
        .tag("pay.success", "true")
        // 在当前时间点添加Span事件,记录关键行为节点
        .event("order_saved");

2.3.3 链路事件记录

向当前链路追加时序事件,自动携带系统时间戳,用于标记链路关键节点(如请求开始、数据库查询完成、回调结束等)。

SpanCustomizer event(String value)

java 复制代码
/**
 * 在 Span 上添加事件。
 * @param value 事件名称
 * @return 当前 {@link SpanCustomizer} 对象,支持链式调用
 */
SpanCustomizer event(String value);

2.4 与 Span 的核心区别

  • SpanCustomizer:被动修改当前链路,无法创建、启停、结束 Span,适合通用组件

  • Span:完整生命周期管控,可创建、启动、结束、丢弃链路单元,适合业务主动埋点

2. Span:顶层核心

2.1 核心定义

SpanMicrometer Tracing 分布式链路追踪的核心顶层接口 ,继承自 SpanCustomizer。代表链路中单次独立工作单元,拥有完整生命周期:创建、启动、埋点、异常记录、结束、上报/丢弃。

该接口设计大量参考 OpenZipkin Brave,同时完全兼容 OpenTelemetry 语义,是 Micrometer 可插拔追踪架构的核心抽象。

相关核心概念:

  • Trace:一条完整分布式调用链,由多个父子关联的 Span 组成
  • Span:单次独立操作单元,链路最小埋点单元
  • Span.Kind:标记 Span 角色,区分客户端、服务端、消息生产/消费
  • Span.BuilderSpan 构建器,支持启动前精细化配置
  • NOOP:空实现实例,关闭追踪时使用,无上报开销

常量定义:

java 复制代码
Span NOOP = new Span() { ... };

空操作实现,关闭追踪时返回该实例。所有埋点操作不向监控后端上报数据,但依然可以正常传递链路上下文,可通过 isNoop() 判断并跳过昂贵计算。

2.2 方法定义

2.2.1 状态与上下文获取

boolean isNoop():判断是否为空 Span ,开启追踪返回 false ,关闭返回 true

TraceContext context():获取链路上下文,包含 traceIdspanId、父链路 ID 等核心信息

java 复制代码
    /**
     * @return 返回 {@code true} 代表当前Span不会执行数据采集、不会上报到外部系统。
     * 但该Span上下文仍需要注入到下游请求中。开发者可利用该标识规避昂贵计算逻辑。
     */
    boolean isNoop();

    /**
     * @return 获取当前Span对应的追踪上下文 {@link TraceContext}
     */
    TraceContext context();

简单示例:

java 复制代码
    public void handleBusiness() {
        Span span = tracer.nextSpan().name("handleBusiness").start();

        try {
            // ======================核心演示======================
            if (!span.isNoop()) {
                // 非空Span:需要采集,执行较重的标签组装逻辑
                String payload = loadHeavyBizData();
                span.tag("request.payload", payload);
                span.tag("biz.type", "order");
            }
            // ====================================================
            doProcess();
        } catch (Exception e) {
            span.error(e);
        } finally {
            span.end();
        }
    }
    
    public void queryOrder() {
        Span span = tracer.nextSpan().name("queryOrder").start();

        try {
            // 获取追踪上下文
            TraceContext traceContext = span.context();

            // 获取链路唯一标识、当前SpanId、父SpanId
            String traceId = traceContext.traceId();
            String spanId = traceContext.spanId();
            String parentSpanId = traceContext.parentSpanId();

            // 日志打印链路ID,方便日志与链路联动排查
            System.out.printf("traceId=%s, spanId=%s%n", traceId, spanId);

            // 传递TraceContext给异步线程场景(手动上下文传播)
            asyncTask(traceContext);

        } finally {
            span.end();
        }
    }

2.2.2 完整生命周期管控

(1)构建阶段

调用 Tracer 构建 Span 对象,此时 Span 尚未启动,大部分核心属性允许配置,一旦 start() 之后,部分底层实现不允许修改父上下文、Kind 等参数。

构建阶段支持调用的方法:

java 复制代码
Builder name(String name);
Builder event(String value);
Builder tag(String key, String value);
Builder tag(String key, long value);
Builder tag(String key, double value);
Builder tag(String key, boolean value);
Builder tagOfStrings(...);
Builder tagOfLongs(...);
Builder tagOfDoubles(...);
Builder tagOfBooleans(...);
Builder error(Throwable throwable);
Builder kind(Span.Kind spanKind);
Builder remoteServiceName(String remoteServiceName);
Builder remoteIpAndPort(String ip, int port);
Builder startTimestamp(long startTimestamp, TimeUnit unit);
Builder addLink(Link link);

示例代码:

java 复制代码
        // 1. Builder构造阶段:配置参数,Span还未启动
        Span span = tracer.nextSpan()
                .name("order.create")
                .kind(Kind.SERVER)
                .tag("order.channel", "app")
                // 启动Span
                .start();
(2)启动阶段

启动 Span,记录起始时间戳,支持链式调用,标记 Span 正式开始,返回可用 Span 对象。

java 复制代码
        /**
         * 构建并启动Span
         * @return 已启动完成的Span实例
         */
        Span start();
(3)运行阶段

链式调用 name() / tag() / event() / error() 追加信息,可设置远端服务信息 remoteServiceName()remoteIpAndPort()

运行阶段支持调用的方法:

java 复制代码
Span name(String name);
Span event(String value);
Span event(String value, long time, TimeUnit timeUnit);
Span tag(String key, String value);
Span tag(key,long/double/boolean);
Span tagOfXXX 系列
Span error(Throwable throwable);
Span remoteServiceName(String remoteServiceName);
Span remoteIpAndPort(String ip, int port);

代码示例:

java 复制代码
            // 2. 运行阶段:业务执行过程动态追加标签、事件
            if (!span.isNoop()) {
                span.tag("order.id", "10086");
                span.event("receive_order_request");
            }
(4)终止阶段
  • void end():结束 Span,自动记录结束时间并上报链路数据
  • void end(long time, TimeUnit timeUnit):自定义时间戳结束 Span ,适配异步、回调场景
  • void abandon():丢弃当前 Span,结束但不上报数据

完整示例:

java 复制代码
        // 1. Builder构造阶段:配置参数,Span还未启动
        Span span = tracer.nextSpan()
                .name("order.create")
                .kind(Kind.SERVER)
                .tag("order.channel", "app")
                // 启动Span
                .start();

        try {
            // 2. 运行阶段:业务执行过程动态追加标签、事件
            if (!span.isNoop()) {
                span.tag("order.id", "10086");
                span.event("receive_order_request");
            }

            doBusiness();

            span.event("business_finish");
        } catch (Throwable t) {
            // 3. 捕获异常,记录异常信息
            span.error(t);
            throw t;
        } finally {
            // 4. 【强制】生命周期收尾,正常上报Span
            span.end();
        }

2.3 内部类/内部接口

2.3.1 枚举 Span.Kind(链路类型)

用于定义 Span 角色,区分上下游调用关系,对齐 OpenTelemetry 规范:

  • SERVER:服务端,接收 RPC/HTTP 远程请求
  • CLIENT:客户端,发起远程调用
  • PRODUCER:消息生产者,向消息队列推送消息
  • CONSUMER:消息消费者,消费队列消息

消息队列场景的生产/消费 Span 无直接关键路径延迟关系,区别于普通客户端服务端调用。

2.3.2 接口 Span.Builder( Span 构建器)

用于Span启动前 精细化配置参数,解决 Span 启动后部分属性无法修改的问题,适配上下文提取、异步链路等复杂场景。

核心能力 :指定父链路、清空父链路、预配置名称/标签/事件/异常、指定 Span 类型、远端信息、自定义启动时间、跨链路关联。

内置 Builder.NOOP 空实现,关闭追踪时无开销。

3. OtelSpan (OpenTelemetry 桥接实现)

3.1 核心定义

OtelSpanSpan 接口的OpenTelemetry SDK 桥接实现类 。属于 Micrometer Tracing 适配层核心组件。

核心设计思想 :上层业务面向 Micrometer 统一抽象编程,底层无缝委托 OTel 原生 SDK 实现能力,实现追踪框架可插拔、底层 SDK 解耦。

3.2 核心成员变量

  • io.opentelemetry.api.trace.Span delegateOTel 原生 Span 委托对象,所有底层能力最终由该对象实现
  • OtelTraceContext otelTraceContextOTel 链路上下文包装对象,封装 OTel ContextSpanContext

3.3 核心常量

OTel 规范标准属性,用于存储下游远端服务名称。

java 复制代码
static final AttributeKey<String> PEER_SERVICE = AttributeKey.stringKey("peer.service");

3.4 静态转换工具方法

提供双向转换能力,实现 Micrometer SpanOTel 原生 Span 无缝互通:

  • toOtel(Span span)micrometer SpanOTel 原生 Span
  • fromOtel(span)OTel 原生 Span 包装为 micrometer OtelSpan
  • fromOtel(span, context):携带自定义 OTel 上下文包装 Span

3.5 方法定义

3.5.1 构造方法

  • 基于 OTel 原生 Span 构建:自动初始化链路上下文

  • 携带 OTel Context 构建:适配手动上下文传递场景

  • 基于 OtelTraceContext 构建:复用已有上下文缓存

java 复制代码
    public OtelSpan(io.opentelemetry.api.trace.Span delegate) {
        this.delegate = delegate;
        this.otelTraceContext = new OtelTraceContext(delegate.getSpanContext(), delegate);
    }

    public OtelSpan(io.opentelemetry.api.trace.Span delegate, Context context) {
        this.delegate = delegate;
        this.otelTraceContext = new OtelTraceContext(context, delegate.getSpanContext(), delegate);
    }

    public OtelSpan(OtelTraceContext traceContext) {
        this.delegate = traceContext.span != null ? traceContext.span : io.opentelemetry.api.trace.Span.current();
        this.otelTraceContext = traceContext;
    }

3.5.2 状态与上下文

boolean isNoop():底层映射 OTel delegate.isRecording(),未采集则为 NOOPSpan

OtelTraceContext context():返回包装后的 OTel 链路上下文

java 复制代码
    @Override
    public boolean isNoop() {
        return !this.delegate.isRecording();
    }

    @Override
    public OtelTraceContext context() {
        if (this.delegate == null) {
            return null;
        }
        return this.otelTraceContext;
    }

3.5.3 生命周期实现(重点特性)

Span start()空实现OTel SpanBuilder 创建时已自动启动,无需重复启动。

void end():结束 Span,若状态为 UNSET 自动填充 StatusCode.OK 成功状态。

void end(time, unit):自定义时间戳结束 Span,自动补全成功状态。

void abandon()空实现OTel SDK 无丢弃不上报语义,该方法不生效。

java 复制代码
    @Override
    public Span start() {
        // they are already started via the builder
        return this;
    }
    
    @Override
    public void end(long time, TimeUnit timeUnit) {
        if (this.isStatusUnset()) {
            this.delegate.setStatus(StatusCode.OK);
        }
        this.delegate.end(time, timeUnit);
    }

3.5.4 标签、事件、名称实现

所有基础能力全部委托 OTel 原生 API

  • 名称修改:updateName()

  • 事件添加:addEvent()

  • 单值标签:setAttribute() 适配多基础数据类型

核心优化 :集合标签不做字符串拼接,直接使用 OTel 原生数组类型 AttributeKey 存储,完全贴合 OTel 数据规范。

3.5.5 异常埋点

Span error(Throwable throwable):调用 OTel recordException() 记录异常堆栈,强制设置 Span 状态为 StatusCode.ERROR,携带异常信息。

3.5.6 远端服务埋点

remoteServiceName:写入 peer.service 标准属性

remoteIpAndPort:写入 OTel 规范网络属性 network.peer.addressnetwork.peer.port

3.5.7 内部辅助方法

isStatusUnset():判断 Span 状态是否未初始化,用于 end 时自动补全默认成功状态。

3.5.8 通用方法覆写

重写 toString、equals、hashCode,基于底层 OTel 委托对象做相等判断,兼容包装类拆包对比。

相关推荐
Gorway2 小时前
理解 Spring 依赖注入:从构造器注入到集合与条件 Bean
java·后端
LiLiYuan.2 小时前
【字符串常量池】
java·开发语言·面试
Sylvia33.2 小时前
从轮询到推送:足球数据API架构演进与火星数据技术拆解
java·服务器·网络·python·websocket·架构
cfm_29142 小时前
高并发系统缓存全解
java·缓存
16月6日-晴3 小时前
Java面向对象进阶—static
java·开发语言
xiaohaiAIgeo3 小时前
【2026年】ASHRAE 110与EN 14175通风柜测试标准对比:进口与国产品牌性能差距
java·前端·数据库·科普知识
meilindehuzi_a3 小时前
TypeScript面试题:interface 与 type:相同点、核心区别与选择指南
java·ubuntu·typescript
AI人工智能+电脑小能手4 小时前
大白话说Java设计模式-08-建造者模式(业务实战篇)
java·设计模式·建造者模式·架构设计·对象构建
xbgRS4 小时前
java中的线程
java