随着业务愈来愈复杂,企业应用也进入了分布式服务化的阶段,传统的日志监控等方式没法很好达到跟踪调用,排查问题等需求。在谷歌论文《 Dapper,大规模分布式系统的跟踪系统》的指导下,许多优秀的APM应运而生。css
分布式追踪系统发展很快,种类繁多,给咱们带来很大的方便。但在数据采集过程当中,有时须要侵入用户代码,而且不一样系统的 API 并不兼容,这就致使了若是您但愿切换追踪系统,每每会带来较大改动。OpenTracing为了解决不一样的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范。OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。详细介绍见 opentracing文档中文版。html
本文要介绍的就是国人吴晟基于OpenTracking实现的开源项目skywalking(码云、github)。java
针对分布式系统的APM(应用性能监控)系统,特别针对微服务、cloud native和容器化(Docker, Kubernetes, Mesos)架构, 其核心是个分布式追踪系统。git
性能好
针对单实例5000tps的应用,在全量采集的状况下,只增长 10% 的CPU开销。详细评测见《skywalking agent performance test》。github
支持多语言探针web
支持自动及手动探针
自动探针:Java支持的中间件、框架与类库列表
手动探针:OpenTrackingApi、@Trace注解、trackId集成到日志中。apache
agent
探针,用来收集和发送数据到归集器。编程
-javaagent:/path/to/skywalking-agent/skywalking-agent.jar
# 当前的应用编码,最终会显示在webui上。
# 建议一个应用的多个实例,使用有相同的application_code。请使用英文
agent.application_code=Your_ApplicationName
# 每三秒采样的Trace数量
# 默认为负数,表明在保证不超过内存Buffer区的前提下,采集全部的Trace
# agent.sample_n_per_3_secs=-1
# 设置须要忽略的请求地址
# 默认配置以下
# agent.ignore_suffix=.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg
# 探针调试开关,若是设置为true,探针会将全部操做字节码的类输出到/debugging目录下
# skywalking团队可能在调试,须要此文件
# agent.is_open_debugging_class = true
# 对应Collector的config/application.yml配置文件中 agent_server/jetty/port 配置内容
# 例如:
# 单节点配置:SERVERS="127.0.0.1:8080"
# 集群配置:SERVERS="10.2.45.126:8080,10.2.45.127:7600"
collector.servers=127.0.0.1:10800
# 日志文件名称前缀
logging.file_name=skywalking-agent.log
# 日志文件最大大小
# 若是超过此大小,则会生成新文件。
# 默认为300M
logging.max_file_size=314572800
# 日志级别,默认为DEBUG。
logging.level=DEBUG
复制代码
collector
链路数据归集器,数据能够落地ElasticSearch,单机也能够落地H2,不推荐,H2仅做为临时演示用。安全
web
web可视化平台,用来展现落地的数据。bash
ui
单独的开源ui项目,更美观易用。demo
Java Agent
Java agent是用一个简单的jar文件来表示的。跟普通的Java程序很类似,Java agent定义了一些类做为入口点。 这些做为入口点的类须要包含一个静态方法,这些方法会在你本来的Java程序的main方法调用以前被调用:
class MyAgent {
public static void premain(String args, Instrumentation inst) {
// implement agent here ...
}
}
复制代码
关于处理Java agent时最有趣的部分,是premain方法中的第二个参数。这个参数是以一个Instrumentation接口的实现类实例的形式存在的。这个接 口提供了一种机制,可以经过定义一个ClassFileTransformer,来干预对Java类的加载过程。有了这种转设施,咱们就可以在Java类 被使用以前,去实现对类逻辑的强化。
在最基本的用例中,Java agent会用来设置应用属性或者配置特定的环境状态,agent可以做为可重用和可插入的组件。以下的样例描述了这样的一个agent,它设置了一个系统属性,在实际的程序中就可使用该属性了:
public class Agent {
public static void premain(String arg) {
System.setProperty("my-property", “foo”);
}
}
复制代码
若是要使用这个agent,必需要将agent类和资源打包到jar中,而且在jar的manifest中要将Agent-Class属性设置为包含premain方法的agent类。(agent必需要打包到jar文件中,它不能经过拆解的格式进行指定。)接下来,咱们须要启动应用程序,而且在命令行中经过javaagent参数来引用jar文件的位置:
java -javaagent:myAgent.jar -jar myProgram.jar
复制代码
咱们还能够在位置路径上设置可选的agent参数。在下面的命令中会启动一个Java程序而且添加给定的agent,将值myOptions做为参数提供给premain方法:
java -javaagent:myAgent.jar=myOptions -jar myProgram.jar
复制代码
经过重复使用javaagent命令,可以添加多个agent。
可是,Java agent的功能并不局限于修改应用程序环境的状态,Java agent可以访问Java instrumentation API,这样的话,agent就能修改目标应用程序的代码。Java虚拟机中这个不为人知的特性提供了一个强大的工具,有助于实现面向切面的编程。
若是要对Java程序进行这种修改,咱们须要在agent的premain方法上添加类型为Instrumentation的第二个参数。Instrumentation参数能够用来执行一系列的任务,好比肯定对象以字节为单位的精确大小以及经过注册ClassFileTransformers实际修改类的实现。ClassFileTransformers注册以后,当类加载器(class loader)加载类的时候都会调用它。当它被调用时,在类文件所表明的类加载以前,类文件transformer有机会改变或彻底替换这个类文件。按照这种方式,在类使用以前,咱们可以加强或修改类的行为,以下面的样例所示:
public class Agent {
public static void premain(String argument, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined, // 若是类以前没有加载的话,值为null
ProtectionDomain protectionDomain,
byte[] classFileBuffer) {
// 返回改变后的类文件。
}
});
}
}
复制代码
经过使用Instrumentation实例注册上述的ClassFileTransformer以后,每一个类加载的时候,都会调用这个transformer。为了实现这一点,transformer会接受一个二进制和类加载器的引用,分别表明了类文件以及试图加载类的类加载器。
Java agent也能够在Java应用的运行期注册,若是是在这种场景下,instrumentation API容许从新定义已加载的类,这个特性被称之为“HotSwap”。不过,从新定义类仅限于替换方法体。在从新定义类的时候,不能新增或移除类成员,而且类型和签名也不能进行修改。当类第一次加载的时候,并无这种限制,若是是在这样的场景下,那classBeingRedefined会被设置为null。
Byte Buddy
Byte Buddy是开源的、基于Apache 2.0许可证的库,它致力于解决字节码操做和instrumentation API的复杂性。Byte Buddy所声称的目标是将显式的字节码操做隐藏在一个类型安全的领域特定语言背后。经过使用Byte Buddy,任何熟悉Java编程语言的人都有望很是容易地进行字节码操做。 做为Byte Buddy的简介,以下的样例展示了如何生成一个简单的类,这个类是Object的子类,而且重写了toString方法,用来返回“Hello World!”。与原始的ASM相似,“intercept”会告诉Byte Buddy为拦截到的指令提供方法实现:
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
复制代码
详见ByteBuddy做者的《Make agents, not frameworks》及译文《构建Java Agent,而不是使用框架》。
Agent模块
更详细的源码分析见芋道源码-skywailking。