概念
Span
Span 是分布式跟踪系统中一个重要且常用的概念. 可从 Google Dapper Paper 和 OpenTracing 学习更多与 Span 相关的知识.
SkyWalking 从 2017 年开始支持 OpenTracing 和 OpenTracing-Java API, 我们的 Span 概念与论文和 OpenTracing 类似. 我们也扩展了 Span.
Span 有三种类型
1.1 EntrySpan
EntrySpan 代表服务提供者, 也是服务器端的端点. 作为一个 APM 系统, 我们的目标是应用服务器. 所以几乎所有的服务和 MQ-消费者 都是 EntrySpan。可以理解一个进程处理第一个span就是EntrySpan,意思为entiry span 进入服务span。
1.2 LocalSpan
LocalSpan 表示普通的 Java 方法, 它与远程服务无关, 也不是 MQ 生产者/消费者, 也不是服务(例如 HTTP 服务)提供者/消费者。所有本地方法调用都是localSpan,包括异步线程调用,线程池提交任务都是。
1.3 ExitSpan
ExitSpan 代表一个服务客户端或MQ的生产者, 在 SkyWalking 的早期命名为 LeafSpan
. 例如 通过 JDBC 访问DB, 读取 Redis/Memcached 被归类为 ExitSpan.
上下文载体 (ContextCarrier)
为了实现分布式跟踪, 需要绑定跨进程的追踪, 并且上下文应该在整个过程中随之传播. 这就是 ContextCarrier 的职责.
以下是有关如何在 A -> B
分布式调用中使用 ContextCarrier 的步骤.
- 在客户端, 创建一个新的空的
ContextCarrier
. - 通过
ContextManager#createExitSpan
创建一个 ExitSpan 或者使用ContextManager#inject
来初始化ContextCarrier
. - 将
ContextCarrier
所有信息放到请求头 (如 HTTP HEAD), 附件(如 Dubbo RPC 框架), 或者消息 (如 Kafka) 中,详情可以看官方给出跨进程传输协议sw8 - 通过服务调用, 将
ContextCarrier
传递到服务端. - 在服务端, 在对应组件的头部, 附件或消息中获取
ContextCarrier
所有内容. - 通过
ContestManager#createEntrySpan
创建 EntrySpan 或者使用ContextManager#extract
将服务端和客户端的绑定.
让我们通过 Apache HttpComponent client 插件和 Tomcat 7 服务器插件演示, 步骤如下:
- 客户端 Apache HttpComponent client 插件
1 | span = ContextManager.createExitSpan("/span/operation/name", contextCarrier, "ip:port"); |
- 服务端 Tomcat 7 服务器插件
1 | ContextCarrier contextCarrier = new ContextCarrier(); |
上下文快照 (ContextSnapshot)
除了跨进程, 跨线程也是需要支持的, 例如异步线程(内存中的消息队列)和批处理在 Java 中很常见, 跨进程和跨线程十分相似, 因为都是需要传播上下文. 唯一的区别是, 不需要跨线程序列化.
以下是有关跨线程传播的三个步骤:
- 使用
ContextManager#capture
方法获取 ContextSnapshot 对象. - 让子线程以任何方式, 通过方法参数或由现有参数携带来访问 ContextSnapshot
- 在子线程中使用
ContextManager#continued
。
跨进程Span传输原理
1 | public class CarrierItem implements Iterator<CarrierItem> { |
CarrierItem 类似Map key value的数据接口,通过一个单向连接将K/V连接起来。
看下 ContextCarrier.items()方法如何创建CarrierItem
1 | public CarrierItem items() { |
创建一个链接CarrierItemHead->SW8CarrierItem ->SW8CorrelationCarrierItem->SW8ExtensionCarrierItem
在看下上面tomcat7 遍历CarrierItem,调用key从http header获取值设置到对象内置值,这样就可以做到将上一个进程header 值设置到下一个进程里,在调用
1 | ContextCarrier deserialize(String text, HeaderVersion version) { |
这样刚刚new 出来ContextCarrier就可以从上一个调用者上继承所有的属性,新创建span就可以跟上一个span 关联起来了了。
开发插件
知识点
追踪的基本方法是拦截 Java 方法, 使用字节码操作技术(byte-buddy)和 AOP 概念. SkyWalking 包装了字节码操作技术并追踪上下文的传播, 所以你只需要定义拦截点(换句话说就是 Spring 的切面)。
ClassInstanceMethodsEnhancePluginDefine
定义了构造方法 Contructor 拦截点和 instance method 实例方法拦截点,主要有三个方法需要被重写
1 | /** |
ClassMatch 以下有四种方法表示如何去匹配目标类:
NameMatch.byName
, 通过类的全限定名(Fully Qualified Class Name, 即 包名 + . + 类名).ClassAnnotationMatch.byClassAnnotationMatch
, 根据目标类是否存在某些注解.MethodAnnotationMatchbyMethodAnnotationMatch
, 根据目标类的方法是否存在某些注解.HierarchyMatch.byHierarchyMatch
, 根据目标类的父类或接口
ClassStaticMethodsEnhancePluginDefine
定义了类方法 class 静态method 拦截点。
1 | public abstract class ClassStaticMethodsEnhancePluginDefine extends ClassEnhancePluginDefine { |
InstanceMethodsInterceptPoint
普通方法接口切点有哪些方法
1 | public interface InstanceMethodsInterceptPoint { |
在看下拦截器有那些方法
1 | /** |
开发Skywalking实战
项目maven环境配置
1 |
|
为了更有代表性一些,使用Skywalking官方开发的ES插件来做一个例子。为了兼容不同版本框架,Skywalking 官方使用witnessClasses,当前框架Jar存在这个Class就会任务是某个版本、同样witnessMethods当Class存在某个Method。
1 | public class AdapterActionFutureInstrumentation extends ClassEnhancePluginDefine { |
创建一个给定类名的拦截器,实现InstanceMethodsAroundInterceptor
接口。创建一个EntrySpan
1 | public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor { |
开发完成拦截器后,一定要在类路径上添加skywalking-plugin.def
文件,将开发后的全类名添加到配置。
xxxName = tk.shenyifeng.skywalking.plugin.RepladInstrumentation
如果jar 里面没有这个文件,插件不会被Skywalking加载的。
最后将打包的jar 放到Skywalking的plugin或者activations目录就可以了。
xml配置插件
1 |
|
通过xml配置可以省去编写Java代码,打包jar步骤
配置 | 说明 |
---|---|
class_name | 需要被增强Class |
method | 需要被增强Method,支持参数定义 |
operation_name | 操作名称 |
operation_name_suffix | 操作后缀,用于生成动态operation_name |
tag | 将在local span中添加一个tag。key的值需要在XML节点上表示 |
log | 将在local span中添加一个log。key的值需要在XML节点上表示 |
arg[n] | 表示输入的参数值。比如args[0]表示第一个参数 |
.[n] | 当正在被解析的对象是Array或List,你可以用这个表达式得到对应index上的对象 |
.[‘key’] | 当正在被解析的对象是Map, 你可以用这个表达式得到map的key |
在配置文件agent.config中添加配置:
plugin.customize.enhance_file=customize_enhance.xml的绝对路径
引用资料
https://www.itmuch.com/skywalking/apm-customize-enhance-plugin/
https://skyapm.github.io/document-cn-translation-of-skywalking/zh/6.1.0/guides/Java-Plugin-Development-Guide.html