SkyWalking简介
定义
SkyWalking是一个开源的应用程序性能监控(Application Performance Monitoring)系统,用于自动捕获,分析和可视化分布式系统中所有组件的指标数据。它为分布式系统提供端到端的链路追踪能力,并涵盖了入口(Ingress)、服务(Service)和出口(Egress)这三个关键部分。
历史背景
在单体应用程序时代,程序运行状况的监控和故障诊断相对简单。但随着微服务架构和云原生应用的兴起,单个系统被拆分为多个分布在不同主机的服务,调用链路变得更加复杂。传统的监控方式已经无法完整地追踪分布式系统中的调用链路,难以快速定位系统故障根源。这促使端到端分布式追踪技术应运而生。
创新点
SkyWalking作为分布式系统的APM解决方案,其最大创新在于提供了全链路跟踪的能力。通过自动注入探针,SkyWalking可以无侵入地捕获分布在多个进程甚至跨主机的链路数据,从而还原整个分布式系统的拓扑结构和调用链路。这使得开发者能全面观测系统中各类指标,快速定位故障根源。
发展趋势
随着云原生技术的不断发展,基于容器和服务网格的微服务架构将更加普及。端到端分布式追踪作为关键支撑技术,必将不断完善以适应更加复杂的分布式场景。具体来说,分布式追踪将向无侵入化、智能化、实时化发展,并与AIOps等技术融合,为自动化运维提供强力支持。
技术能做什么
核心优势
- 1. 端到端追踪:能追踪链路的完整调用过程,自动发现拓扑结构和依赖关系。
- 2. 性能指标监控:收集并可视化系统各层级指标,如吞吐量、延迟、慢查询等。
- 3. 链路数据分析:基于链路指标进行聚合分析,提供延迟最大节点、调用频次等高价值指标。
- 4. 故障快速定位:让开发人员能有效分析和诊断系统故障。
- 5. 架构可视化:以图形化拓扑展示整个分布式系统的架构。
技术实现
- 1. 探针注入:通过字节码注入或SDK集成的方式,在目标系统植入Agent,采集链路数据。
- 2. 数据传输:Agent通过gRPC等方式将追踪数据传输至SkyWalking后端。
- 3. 链路数据分析:后端对采集到的分布式链路数据进行解析、构建和聚合分析。
- 4. 指标存储:使用存储引擎如ElasticSearch对指标和拓扑结构进行持久化存储。
- 5. 可视化展现:提供基于Web UI的可视化界面,呈现分布式链路、应用拓扑等数据。
实现原理
工作流程
- 1. 启动时:SkyWalking前端分别启动OAP(Operational Application Platform)集群与UI服务。
- 2. 发现阶段:在监控目标系统启动时,Agent根据SkyWalking配置选择加载探针。
- 3. 数据采集:探针自动注入到应用程序中,采集链路数据并发送至OAP集群。
- 4. 分析处理:OAP集群对收集到的链路数据进行解析、构建链路对象和指标计算。
- 5. 存储同步:OAP集群定期将分析结果数据同步至持久层存储引擎。
- 6. 数据查询:UI服务从存储引擎查询数据后,进行可视化展现。
关键概念
- 1. 字节码增强:通过Java Agent,SkyWalking使用字节码增强技术(如ASM)在应用启动时对入口、出口及所有可能被调用的方法插入探针代码。
- 2. Trace和Span创建:当新请求进入时,创建Trace对象表示整个分布式链路调用过程。每个被增强方法执行前,创建Span记录方法元数据(如方法名、参数等)和时间信息,内嵌调用形成有向无环树状Span结构。
- 3. Span数据收集:方法执行完毕后,探针获取执行时间等信息填充到对应Span中,完整记录该方法执行情况。
- 4. Segment上报:每个应用实例内的所有Span数据打包成一个Segment,并独立上报给SkyWalking后端,减小单实例数据压力。
- 5. 数据解析与分析:后端根据标识将来自不同实例的Segment数据组装为完整的Trace,进行存储、分析,生成链路拓扑、指标数据等,并可视化展示每个Span详情,用于分析整体性能瓶颈。
底层机制
- 1. 字节码增强:利用字节码操作技术,对应用代码进行无侵入式增强,植入追踪代码。
- 2. 跨进程传递:通过SW8运行传播跟踪上下文实现分布式追踪。
- 3. 采样策略:基于算法和规则采集链路数据,减少性能消耗。
- 4. 指标聚合:聚合和分析链路指标,提炼出如总体成功率等高价值指标。
技术使用
使用步骤
- 1. 部署后端:下载并启动OAP集群和UI服务,可通过Docker Compose快速部署。
- 2. 配置Agent:修改Agent配置文件,指定OAP集群地址等参数。
- 3. 探针加载:通过手动下发探针或应用自动扫描等方式,在目标系统加载SkyWalking探针。
- 4. 性能优化:适当调整采样率、探针配置,减少对应用性能的影响。
- 5. 登录UI:访问SkyWalking UI查看链路拓扑、指标等数据。
目的和结果
- o 目标系统透明植入监控能力,无需侵入代码
- o 全方位监控系统各项指标,如延迟、吞吐量等
- o 自动发现整体系统架构和依赖拓扑关系
- o 追踪分布式链路,快速定位性能瓶颈和故障根源
常见问题
- o Agent与OAP集群连接异常,导致数据上报失败
- o UI无法正常显示链路数据,可能由于ElasticSearch等存储异常
- o 监控数据不完整,可能由于Agent配置或采样策略问题
- o 应用性能受到较大影响,需要调整Agent配置和优化探针
最佳实践及注意事项
最佳实践
- o 合理配置采样率和忽略规则,避免采集过多无用数据
- o 基于链路分析定制高价值指标,用于专项监控
- o 与应用程序集成,实现链路数据可视化和自动告警
- o 利用链路数据深入分析,定期审查系统架构和潜在瓶颈
- o 结合压测分析,提前发现系统瓶颈,确保架构合理
注意事项
- o Agent需确保与OAP集群正常通信,否则数据会丢失
- o 监控对象变更时需更新Agent配置,新服务需重新加载
- o 探针扫描可能与某些框架或类库存在兼容性问题
- o 链路数据默认7天过期,如需长期存储需提前规划
代码示例
手动加载Agent
# Linux命令启动应用,并指定加载SkyWalking Agent
$ AGENT_PATH=/PATH/TO/skywalking-agent/skywalking-agent.jar
$ java -javaagent:$AGENT_PATH -jar YOUR_APP.jar
应用启动脚本示例:
#!/usr/bin/env bash
# 检查运行环境
checkJava(){
if [ ! -x "$JAVA_HOME/bin/java" ]; then
echo "Cannot find Java. Please set JAVA_HOME correctly." 1>&2
exit 1
fi
}
# 加载SkyWalking Agent
loadAgent(){
AGENT_PATH=/opt/skywalking/agent
if [ -r "$AGENT_PATH/skywalking-agent.jar" ]; then
JAVA_OPTS=" -javaagent:$AGENT_PATH/skywalking-agent.jar"
fi
}
# 启动应用
startApp(){
checkJava #检查Java环境
loadAgent #加载Agent
exec "$JAVA_HOME/bin/java" $JAVA_OPTS -jar /opt/app/app.jar
}
startApp
SpringBoot应用自动加载
# application.yaml配置
skywalking:
agent:
service-name: yourAppName # 设置服务名称
instance: prod # 设置实例名称
Docker镜像植入
FROM openjdk:8-jre
# 拷贝应用程序文件
COPY mall-app.jar /data/apps/
# 拷贝SkyWalking Agent
ENV AGENT_PATH=/opt/skywalking/agent
COPY $AGENT_PATH $AGENT_PATH
# 设置JVM参数
ENV JAVA_OPTS="-javaagent:${AGENT_PATH}/skywalking-agent.jar"
# 暴露端口
EXPOSE 8080
# 应用入口
ENTRYPOINT ["java", "-jar", "/data/apps/mall-app.jar"]
功能点解析:
- o 手动加载:通过命令-javaagent参数指定需要加载的Agent路径,实现手动加载。
- o 自动加载:在SpringBoot应用的application.yaml中配置skywalking.agent相关属性,实现自动加载。
- o 容器加载:在Dockerfile中,先将Agent拷贝进镜像,再通过设置JAVA_OPTS参数加载Agent。
- o 这些示例涵盖了Java应用最常见的三种加载Agent的场景。其中,关键在于利用-javaagent参数指定Agent路径,SkyWalking就会在目标应用启动时自动植入并开始探针扫描。这实现了对系统无侵入的监控能力。
进阶用法
如何实现链路追踪
默认情况下,SkyWalking Agent会自动追踪以下几个方面:
- o SpringMVC:SkyWalking会自动追踪SpringMVC的Controller请求,包括请求的入口、Controller方法的执行、模板渲染等,能记录整个请求的耗时。
- o 远程调用:如果应用内部有远程调用其他应用的行为,比如通过HttpClient、Apache CXF等库发起的远程调用,这些行为也会被自动追踪。
- o 数据库访问:对于像MySQL、Oracle、Redis、MongoDB等常见的数据库和缓存产品,访问它们的操作都会被追踪,能看到具体的访问语句、耗时等。
- o 消息队列:如果使用RabbitMQ、Kafka等中间件做消息传递,发送消息和消费消息的行为也会被追踪。
- 1. 追踪上下文(TraceContext):SkyWalking 使用 TraceContext 来表示一个完整的分布式链路追踪上下文。它包含了 traceId、spanId、parentSpanId 等核心字段,用于标识和关联一个分布式链路中的所有span。
- 2. 上下文传递:TraceContext 需要跨越进程边界,才能将整个分布式链路串起来。SkyWalking 会通过两种方式将 TraceContext 传递给下游服务: 2.1 当使用HTTP调用时,会将 TraceContext 嵌入请求头(sw8表示是否采样) 2.2 当使用RPC框架调用(如Dubbo)时,会将TraceContext作为附加字段传递 接收方从请求中解析出 TraceContext,并和当前线程挂钩,从而将所有span关联起来。
- 3. Span的创建和完善:当一个被增强的方法执行时,SkyWalking 会判断是否需要创建一个新的span。一般在入口点(比如Web容器的servlet的请求入口)和线程入口(异步线程执行)时,会创建全新的span。否则,则基于上层span创建下一级span。span会记录其开始时间、操作名、组件类型等信息。在方法执行结束后,又会补充span的结束时间、日志、异常等信息。层层嵌套的span就构成了整个链路追踪快照。
- 4. Span的上报:完善好的span会被缓存在内存中,由单独的线程定时上报给 SkyWalking OAP 服务端,进行深度分析和展示。
整个链路追踪的实现离不开字节码植入技术,能够无侵入地注入跟踪代码。同时依赖TraceContext在进程间传递,串联起了整个分布式链路。再通过高效的Span管理,SkyWalking实现了全链路的高效追踪。
为什么要自定义追踪行为
SkyWalking的自动探针可以看到一个请求链路上所有方法的耗时情况。但是自动探针也可能无法覆盖的一些场景:
- 1. 异步调用:如果一个方法内发起了异步调用(比如开启新线程),自动追踪可能就捕获不到新线程中的方法执行情况了。
- 2. 跨进程调用:自动探针只能追踪同一个JVM进程内的调用,如果是进程间通过消息队列、RPC等方式的调用,就需要手动添加跟踪上下文的传递。
- 3. 第三方组件:SkyWalking原生支持常见的组件(Tomcat、JDBC等),但对于自定义的第三方组件,可能需要编写额外的插件或手动埋点追踪。
- 4. 特殊的业务场景:对于一些复杂的业务场景和流程,可能需要在代码中手动添加标记以辅助性能分析,比如标记业务关键点、记录业务参数等。
总的来说,自定义追踪的目的主要有两个:
- 1. 扩展和完善自动探针覆盖不到的场景
- 2. 追踪和记录一些特殊的业务上下文信息,以辅助更深入的链路分析和问题诊断
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
public class UserServiceInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, String methodName, Object[] allArguments, Class>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
UserServiceTraceInfo traceInfo = new UserServiceTraceInfo();
traceInfo.setStartTime(System.currentTimeMillis());
traceInfo.setInput(allArguments[0]);
objInst.setSkyWalkingDynamicField(traceInfo);
}
@Override
public Object afterMethod(EnhancedInstance objInst, String methodName, Object[] allArguments, Class>[] argumentsTypes, Object ret) throws Throwable {
UserServiceTraceInfo traceInfo = (UserServiceTraceInfo) objInst.getSkyWalkingDynamicField();
traceInfo.setOutput(ret);
traceInfo.setEndTime(System.currentTimeMillis());
traceInfo.logTraceInfo();
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, String methodName, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
UserServiceTraceInfo traceInfo = (UserServiceTraceInfo) objInst.getSkyWalkingDynamicField();
traceInfo.setException(t);
traceInfo.logTraceInfo();
}
private static class UserServiceTraceInfo {
private long startTime;
private long endTime;
private Object input;
private Object output;
private Throwable exception;
// getter和setter方法...
public void logTraceInfo() {
// 记录追踪信息,例如输出到日志或发送到追踪系统
System.out.println("Method execution time: " + (endTime - startTime) + "ms");
System.out.println("Input: " + input);
System.out.println("Output: " + output);
if (exception != null) {
System.out.println("Exception: " + exception.getMessage());
}
}
}
}
注意事项:
- o 通过实现InstanceMethodsAroundInterceptor接口,我们可以定制方法增强的行为,从而实现自定义的方法追踪。
- o 实现beforeMethod、afterMethod和handleMethodException三个方法,分别在方法调用前、正常返回后和抛出异常时执行追踪逻辑。
- o 可以使用SkyWalking提供的API记录追踪信息,也可以自定义数据结构存储追踪信息。
- o 实现的插件需要配置到SkyWalking Agent的插件目录下,才能生效。
SkyWalking如何追踪异步调用
SkyWalking 利用了 ThreadLocal 和 TraceContext 克隆等技术手段,巧妙地解决了分布式追踪中跨线程传递上下文的难题,使得即使在异步执行的场景下也能够准确地追踪整个分布式调用链路
- 1. 当一个请求进入应用程序时,SkyWalking 的探针会创建一个新的 TraceContext 对象,并将其绑定到当前线程的 ThreadLocal 中。
- 2. 如果这个请求需要异步执行,比如提交到线程池,那么 ThreadPoolExecutor 探针会在 execute() 方法中通过 ContextManager.capture() 方法获取当前线程的 TraceContext。
- 3. ContextManager.capture() 方法会克隆一份当前线程的 TraceContext,并创建一个新的 TraceContext 实例,新实例的 parentSpanId 会设置为当前 TraceContext 的 spanId。
- 4. 这个新创建的 TraceContext 会通过某种方式传递给线程池中的 Worker 线程,常见的做法是将其作为 Runnable 任务的成员变量或者附加在 ThreadLocal 的inheritableThreadLocals 中。
- 5. 当 Worker 线程开始执行异步任务时,它会先通过 ContextManager.get() 方法获取之前传递过来的 TraceContext,并将其绑定到当前线程的 ThreadLocal 中。
- 6. 在异步任务的执行过程中,SkyWalking 的探针可以随时通过 ContextManager.get() 方法获取当前线程的 TraceContext,并记录相关的追踪信息。
- 7. 当异步任务执行完毕后,探针会将收集到的追踪信息发送给后端的 SkyWalking OAP 服务端,并通过 ContextManager.removeContext() 移除当前线程绑定的 TraceContext。
通过这种机制,SkyWalking 就实现了在同一个链路追踪过程中,跨越多个线程时 TraceContext 的传递和获取。由于 ThreadLocal 的存在,每个线程中保存的 TraceContext 实例都是独立的,不会相互影响。同时,由于 TraceContext 在线程之间是通过显式复制和传递的方式获取的,因此也避免了线程安全问题
采集率推荐配置
采集率推荐配置主要取决于应用场景和需求。以下是一些常见的采集率推荐配置:
- 1. 保持异常请求的100%采样率:建议对异常请求(如HTTP 500错误代码)保持100%的采集率,确保能够捕获所有异常场景的链路数据,方便问题排查。可以通过配置agent.sampler.illegalt_status_code_sample_ratio为1.0来实现。
- 2. 适当降低正常请求的采样率:对于正常的业务请求,可以适当降低采样率,减少不必要的性能开销。但不建议采样率设置过低,避免遗漏重要业务链路。一个比较常见的做法是将全局采样率agent.sampler.percentage_sample_ratio=xx ,xx 为 0-100 的整数,表示采样率的百分比。例如设置为 50,表示 50% 的采样率
- 3. 对低价值链路组件降低采样率:利用基于组件的采样率配置,对一些非核心组件(如较为简单的调用链路)适当降低采样率,如MySQL、Redis等。对于核心业务组件则保持高采样率。使用agent.sample_n_per_3_secs --operationName实现。
- 4. 启用自适应采样:SkyWalking提供了基于响应时间的自适应采样策略,可以自动提高较慢链路的采样率。配置agent.sampler.statistic.strategy实现。
- 5. 生产和测试环境分离配置:建议为生产环境配置较高的采样率,以获得更好的可观测性;而测试环境可适当降低采样率,减少开销。
- 6. 监控采样率并及时调整:持续关注SkyWalking UI上展示的实际采样率,如果存在采样率过高或过低的情况,及时调整配置项。
同时结合异常请求 100% 采样、对低价值组件进一步降低采样率等策略,能够较好地权衡可观测性和性能开销