文章目录
- [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:顶层核心)
- [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,底层同时兼容 Brave、OpenTelemetry 两大主流追踪实现。
整个链路追踪体系中,Span 是最核心的工作单元,承担链路埋点、生命周期管理、标签事件记录、异常上报等核心能力。
本文一次性完整拆解三大核心源码组件:
Span:顶层核心接口,定义链路单元完整生命周期与能力SpanCustomizer:轻量Span定制接口,无实例操作当前链路OtelSpan:OpenTelemetry底层桥接实现类
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 核心定义
Span 是 Micrometer Tracing 分布式链路追踪的核心顶层接口 ,继承自 SpanCustomizer。代表链路中单次独立工作单元,拥有完整生命周期:创建、启动、埋点、异常记录、结束、上报/丢弃。
该接口设计大量参考 OpenZipkin Brave,同时完全兼容 OpenTelemetry 语义,是 Micrometer 可插拔追踪架构的核心抽象。
相关核心概念:
Trace:一条完整分布式调用链,由多个父子关联的Span组成Span:单次独立操作单元,链路最小埋点单元Span.Kind:标记Span角色,区分客户端、服务端、消息生产/消费Span.Builder:Span构建器,支持启动前精细化配置NOOP:空实现实例,关闭追踪时使用,无上报开销
常量定义:
java
Span NOOP = new Span() { ... };
空操作实现,关闭追踪时返回该实例。所有埋点操作不向监控后端上报数据,但依然可以正常传递链路上下文,可通过 isNoop() 判断并跳过昂贵计算。
2.2 方法定义
2.2.1 状态与上下文获取
boolean isNoop():判断是否为空 Span ,开启追踪返回 false ,关闭返回 true
TraceContext context():获取链路上下文,包含 traceId、spanId、父链路 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 核心定义
OtelSpan 是 Span 接口的OpenTelemetry SDK 桥接实现类 。属于 Micrometer Tracing 适配层核心组件。
核心设计思想 :上层业务面向 Micrometer 统一抽象编程,底层无缝委托 OTel 原生 SDK 实现能力,实现追踪框架可插拔、底层 SDK 解耦。
3.2 核心成员变量
io.opentelemetry.api.trace.Span delegate:OTel原生Span委托对象,所有底层能力最终由该对象实现OtelTraceContext otelTraceContext:OTel链路上下文包装对象,封装OTel Context与SpanContext
3.3 核心常量
OTel 规范标准属性,用于存储下游远端服务名称。
java
static final AttributeKey<String> PEER_SERVICE = AttributeKey.stringKey("peer.service");
3.4 静态转换工具方法
提供双向转换能力,实现 Micrometer Span 与 OTel 原生 Span 无缝互通:
toOtel(Span span):micrometer Span转OTel原生SpanfromOtel(span):OTel原生 Span 包装为micrometer OtelSpanfromOtel(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(),未采集则为 NOOP 空 Span
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 Span 在 Builder 创建时已自动启动,无需重复启动。
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.address、network.peer.port
3.5.7 内部辅助方法
isStatusUnset():判断 Span 状态是否未初始化,用于 end 时自动补全默认成功状态。
3.5.8 通用方法覆写
重写 toString、equals、hashCode,基于底层 OTel 委托对象做相等判断,兼容包装类拆包对比。