通常,应用接入 APM 后,可以追踪到应用相关组件、服务间的调用链路情况,如 Tomcat、Redis、MySQL 等,这是因为 APM 对于标准性组件做了插桩处理,从而更好的观测到在实际使用过程中组件调用对应用的影响。
而在实际生产过程中,非标代码即业务代码,常常对业务影响程度更深,不同程度的开发人员对代码理解程度、编写能力也存在这差异,对于业务代码,如何追踪完整的类函数调用,找到关键问题所在仍是至关重要。
所幸的是 APM 研发人员也是科班出生,也曾体会过人间疾苦,同样对 APM 赋予了更高的期望。DataDog、OpenTelemetry 也提供了相关的功能,让我们一窥究竟。
以 JAVA 为例,分别探究 DDTrace(DataDog)、OpenTelemetry。
如何使用 DDTrace 追踪函数调用
准备一段代码
@Autowired
private TestService testService;
@GetMapping("/user")
@ResponseBody
public String getUser(){
logger.info("do getUser");
return testService.users();
}
复制代码
Service 接口
public interface TestService {
String users();
}
复制代码
Service 实现类
package com.zy.observable.server.service;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class TestServiceImpl implements TestService {
private static final Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
public String getUsername(){
return "lr";
}
public String users(){
Map<Integer,Student> users =new HashMap<>();
users.put(1,new Student("tom",18));
users.put(2,new Student("joy",20));
users.put(3,new Student("lucy",30));
users.forEach((k,v)->print(k,v));
return getUsername();
}
public void print(Integer level,Student student){
logger.info("level:{},username:{}",level,student.getUsername());
}
}
复制代码
当 Controller 层调用 testService.users()
,默认情况下,users
不会作为链路的一部分,用以下命令运行
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-jar springboot-server.jar
复制代码
或者在 idea 工具进行调试均可。
请求 http://localhost:8090/user
可以观察到链路情况,有两个 span。
如何将 users
方法也体现在链路中,DDTrace 提供了以下参数可以发现业务代码信息。
参数方式:-Ddd.trace.methods
环境变量方式:DD_TRACE_METHODS
用以下命令
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[users]" \
-jar springboot-server.jar
复制代码
如果想追踪一个类中的所有函数调用,则用*
来表示。
java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[*]" \
-jar springboot-server.jar
复制代码
尽管TestService
只有一个接口方法,用通配符*
,仍会出现多个 span 信息。
细心的你可能已经发现了问题,TestServiceImpl
分别提供了三个函数getUsername
、users
、print
,其中 users
分别调用了 getUsername
和 print
,为什么链路上有print
却没有getUsername
?
原因在于 TestServiceImpl
实现了 TestService
接口,而 *
则代表所有,ddtrace 实际是对TestService
的实现类TestServiceImpl
进行了增强处理。相当于-Ddd.trace.methods="com.zy.observable.server.service.TestServiceImpl[*]"
主要是因为 DDTrace
对一些关键方法做了屏蔽,对于以下类型的方法函数将不会做增强处理。
constructors
getters
setters
synthetic
toString
equals
hashcode
finalizer method calls
值得注意的是,以上链路print
函数出现了三个 span,是由于stus
是一个 Map 集合,循环了三次,调用了三次 print
。这在某些场景下是致命的,试想想,如果stus
集合是 1000,print
则会生成对应数量的 span。这不管是查看链路还是成本估算,都有着很高的代价。前者由于 span 太多,会导致浏览器内存占用高,造成 UI 界面卡顿,后者增加了存储和查询成本。
如何使用 OpenTelemetry 追踪函数调用
继续沿用上面的代码,在不添加其参数的情况下,用以下命令运行应用
-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server
复制代码
可以观察到 OpenTelemetry 链路 Span 与 DDTrace 基本一致。
在无法修改代码的情况下,OpenTelemetry 也提供了以下配置 Java 代理来捕获特定方法的跨度。
需要注意的是,otel 不支持同配符的方式进行配置。
运行以下命令
-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server
-Dotel.instrumentation.methods.include="com.zy.observable.server.service.TestService[users]"
复制代码
同样 users
函数也被添加了 span 信息。
SDK 方式
以上分别介绍了 DDTrace 和 OpenTelemetry 在非侵入式的情况下对业务特定函数方法进行链路追踪。同样它们也提供了 SDK 的方式,具有一定的代码侵入性,但表现为更灵活。
关于 SDK 方式,这里不再一一分析。
参考文档
DDTrace Agent 下载地址
OpenTelemetry Agent 下载地址
OpenTelemetry methods
SpringBoot-Server demo
评论