调用链串联
功能要求
1 查看每个span的耗时,用于排查响应时间最慢的节点
2 跨服务业务日志串联
3 查看整个调用链,跨服务的span包括内容。比如访问数据是哪个mybatis Mapper ,SQL是什么。 访问Redis 是哪个客户端
4 可以在浏览器抓包看到traceid。(源头从APP和前端生成traceid)
5 通过agent方式接入,对代码无侵入性
6 业务日志也要输出traceid
7 异步线程池调用,调用链不丢失
8 可配置开关:
支持按PSA设置采样率,失败全部采样,超时全部采样,
是否尊重上游采样:开启:有上游时遵从上游采样状态(上游采样则采样,上游未采样则不采样);无上游时总是根据采样率采样。
关闭:不关注上游采样状态,总是根据采样率进行采样
是否开启Datasource Span
是否开启AccessLog
技术点
异步线程池不丢失traceID
打印日志需要带上traceID
跨服务调用传递traceID
能够抓取SQL,并做敏感数据脱敏
支持各种框架的调用链传输,比如Redis,数据库连接池,Springcloud。并能够在拓扑图清晰可观测。
开源框架
OpenTelemetry
定义与背景:OpenTelemetry 是一个更全面的开源可观测性框架,同样由 CNCF 托管。它整合了追踪(Tracing)、度量(Metrics)和日志(Logging)等多个方面,旨在为云原生应用和分布式系统提供统一的、端到端的可观测性解决方案,是对 OpenTracing 等早期标准的进一步发展和完善。
OpenTracing
定义与背景:OpenTracing 是一个用于分布式追踪的开源标准协议和 API 规范,由 Cloud Native Computing Foundation(CNCF)托管。它旨在为开发人员提供一种与供应商无关的方式来在分布式系统中实现和管理追踪功能,使不同的追踪系统能够更好地兼容和互操作。
使用OpenTracing就不需要Zipkin了
Otel Javaagent
是在OpenTelemetry 社区提供的基于Javaagent技术的自动化埋点组件 opentelemetry-javaagent的基础上进行了改进和优化的版本,包括但不局限于:
Why Javaagent
无侵入:基于Javaagent技术以及Java成熟的字节码注入框架体系,提供轻量级,简单,开箱即用的无痕埋点能力;
场景更丰富:超过 120+ 场景均可无侵入自动完成埋点,涵盖JDK通用场景,流行框架,中间件,开源/自研等多种场景;业务无需写一行代码即可享受丰富的可观测埋点;
平台托管:通过与CI、CD、k8s、配置中心等平台方协同完成Javaagent生命周期管理;用户无需写一行代码,只需简单的开启即可完成Javaagent的接入,版本管理,注入,配置等
业务与基础设施解耦:业务无需引入Javaagent代码,其迭代,升级,bug修复等均由平台完成;让业务关注业务实现;
大势所趋:基于当前行业OpenTelemetry建立统一的可观测标准规范,行业趋势;
注意
Otel Javaagent是面向普通服务设计并开发,如果您的服务是特殊场景下的专有系统(如:中间件类,存储类,网络基础设施等比较底层和精密的组件),请在评估风险,难度,投入之后酌情考虑是否使用(比如RocketMQ虽然是基于Java构建,但是不推荐使用Javaagent进行接入)
自定义埋点
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
<version>1.28.0</version>
</dependency>
public class MyClass {
@WithSpan
public void myMethod(@SpanAttribute("param1") String param1,
@SpanAttribute("param2") long param2) {
// do stuff
}
}
使用注解可以方便的进行自定义埋点,使用@WithSpan加在方法上来定义一个Span,使用@SpanAttribute加在参数上来定义要上传的Attribute。
@SpanAttribute上报的属性会使用当前对象toString()方法取值。
Otel Javaagent和Spring Cloud OpenFeign 集成
实现原理
- 字节码增强:Otel Javaagent 利用 Java Instrumentation API 对目标应用程序的字节码进行增强。在应用启动时,Javaagent 会在类加载阶段对特定的类进行修改,插入用于收集追踪信息的代码,而无需修改应用的源代码。
- 集成 OpenFeign 拦截器:OpenTelemetry 为 OpenFeign 提供了拦截器,这些拦截器会在 OpenFeign 发起请求和接收响应的过程中被触发,从而收集请求和响应的相关信息,如请求方法、URL、响应状态码等,并将其转换为 OpenTelemetry 的 Span(跨度)数据。
- 分布式追踪上下文传播:在服务调用过程中,Otel Javaagent 会自动将追踪上下文(如 Trace ID、Span ID)通过请求头的方式在不同服务之间进行传播,使得整个调用链中的各个服务能够关联起来,形成完整的分布式追踪。
1. 添加依赖
在 Spring Boot 项目的 pom.xml
中添加 OpenTelemetry 相关依赖:
<dependencies>
<!-- OpenTelemetry API -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.29.0</version>
</dependency>
<!-- OpenTelemetry SDK -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.29.0</version>
</dependency>
<!-- OpenTelemetry Spring Cloud OpenFeign 自动配置 -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-spring-cloud-openfeign-3.0</artifactId>
<version>1.29.0-alpha</version>
</dependency>
<!-- OpenTelemetry 自动配置扩展 -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.29.0</version>
</dependency>
</dependencies>
2. 配置 Javaagent
下载 OpenTelemetry Javaagent 的 JAR 文件,可以从 OpenTelemetry 官方发布页面 获取。
在启动应用时,通过 -javaagent
参数指定 Javaagent 的路径,并设置相关环境变量:
java -javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.service.name=your-service-name \
-Dotel.exporter.otlp.endpoint=http://your-otlp-endpoint:4317 \
-jar your-application.jar
-Dotel.service.name
:指定服务名称,用于在追踪系统中标识当前服务。-Dotel.exporter.otlp.endpoint
:指定 OTLP(OpenTelemetry Protocol) 导出器的端点地址,用于将追踪数据发送到后端追踪系统(如 Jaeger、Zipkin 等)。
3. 配置 Spring Cloud OpenFeign
确保你的项目中已经正确配置了 Spring Cloud OpenFeign。例如,定义一个 OpenFeign 客户端接口:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "example-service")
public interface ExampleFeignClient {
@GetMapping("/example")
String getExample();
}
4. 验证自动埋点
启动应用程序,当使用 OpenFeign 客户端发起请求时,Otel Javaagent 会自动进行埋点,并将追踪数据发送到指定的后端追踪系统。你可以通过访问后端追踪系统的 UI 界面(如 Jaeger 的 http://localhost:16686
)来查看分布式追踪信息,包括请求的调用链、每个服务的执行时间等。
注意事项
- 版本兼容性:确保 OpenTelemetry 相关依赖的版本与你的 Spring Boot 和 Spring Cloud 版本兼容,避免出现版本冲突问题。
- 配置调整:根据实际需求,可以调整 OpenTelemetry 的配置,如采样率、导出器类型等,以满足不同的监控和追踪需求。
全链路日志
Opentelemetry使用日志MDC,支持trace_id,span_id,trace_flags变量
MDC(Mapped Diagnostic Context,映射调试上下文)是日志框架提供的一种方便在多线程条件下记录日志的功能。简而言之,MDC就是日志框架提供的一个InheritableThreadLocal,项目代码中可以将键值对放入其中,MDC 中包含的内容可以被同一线程中执行的代码所访问。
修改日志打印框架配置:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] STD [%X{trace_id}] [%X{trace_flags}] [%level] [%thread] [%logger{56}] %msg%n</pattern>
</encoder>
</appender>
实现原理
实际使用用Otel无缝对接,直接增加依赖接口。
traceid跨微服务传递
微服务接到请求,把traceId塞到theadlocal
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class TraceIdInterceptor implements HandlerInterceptor {
@Autowired
private Tracer tracer;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取当前活动的 Span
Span currentSpan = tracer.getCurrentSpan();
// 获取 traceId
String traceId = currentSpan.getSpanContext().getTraceId();
// 将 traceId 存入 ThreadLocal
TraceIdContext.setTraceId(traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求处理完成后清除 ThreadLocal 中的 traceId
TraceIdContext.clearTraceId();
}
}
微服务调用下个微服务
1 传递traceId
@Slf4j
@Configuration
public class FeignTracingInterceptor {
@Bean
RequestInterceptor feignTraceInterceptor() {
return template -> {
if (hasTraceId()) {
template.header("traceId", TraceThreadLocal.getTraceId());
template.header("level", TraceThreadLocal.getSpanId());
};
}
}
2 成功,异常上报
基于AOP做拦截
跨服务调用成功或者失败Span记录
@Slf4j
@Aspect
@Component
public class FeignTracingConfig {
@Around("@within(org.springframework.cloud.openfeign.FeignClient)")
public Object clientTrace(ProceedingJoinPoint joinPoint) throws Throwable {
boolean hasTraceId = false;
try {
hasTraceId = hasTraceId();
} catch (Exception e) {
log.error("TraceManager.hasTraceId() error,", e);
}
if (!hasTraceId) {
return joinPoint.proceed();
}
TraceSegment traceSegment = ...
try {
Object result = joinPoint.proceed();
traceSegment.statusOK();
return result;
} catch (Throwable e) {
traceSegment.statusError(e);
throw e;
}
}
}
异步线程传递调用链
框架定义TraceRunable类,在线程run()方法之前,取当前线程上下文。 设置到当前上下文,执行完还原上下文
class TraceRunnable implements Runnable {
private TraceScope scopeInfo = TraceThreadLocal.getScope();
private Runnable command;
public TraceRunnable(Runnable command) {
this.command = command;
}
public void run() {
TraceScope oldValue = TraceThreadLocal.getScope();
if (this.scopeInfo == null) {
this.command.run();
} else {
TraceSegment segment = ...
TraceThreadLocal.setScope(segment.getScope());
try {
this.command.run();
segment.statusOK();
} catch (RuntimeException var7) {
segment.statusError(var7);
throw var7;
} finally {
TraceThreadLocal.setScope(oldValue);
}
}
}
}
自定义TraceScheduledExecutorService 扩展ScheduledExecutorService 。 当传入的非TraceRunable类,自动做一层包装
用法:用户使用TraceScheduledExecutorService代替JDK默认的工具类,就可以实现traceid的传递。
底层代码
TraceScheduledExecutorService
public void execute(Runnable command) {
super.execute(new TraceRunnable(command));
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南