Skywalking插件开发指南
这篇文档主要介绍理解,开发和贡献插件
概念
Span(跨度)
在分布式的链路追踪系统里面Span是一个重要而又普遍的概念。我们可以从Google Dapper Paper 和OpenTracing学习span的相关知识
Skywalking从2017年就支持OpenTracing和OpenTracing-Java API。我们的Span概念和OpenTracing以及google的论文里面的概念非常相思。而且我们也扩展了Span。
这里有三中类型的Span
1.1 EntrySpan
EntrySpan代表了一个服务提供者,也就是服务端。作为一个APM系统,我们关注应用服务器。所以几乎所有的服务和MQ的消费端都是EntrySpan。
1.2 LocalSpan
LocalSpan可以理解为一个和远程服务无关的普通的Java方法,且这个Java方法即不是MQ的生产者,也不是消费者,更不是一个HTTP服务的生产者和消费者。
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
的信息放入到head(Http head),attachments(Dobbo RPC框架)或者messages(Kafka)中。通过服务调用,
ContextCarrier
传播到服务端。在服务端可以通过heads/attachments/messages获取到
ContextCarrier
的所有信息。ContextManager#createEntrySpan
方法会创建一个EntrySpan
或者使用ContextManager#extract
方法来将客户端和服务器绑定到一起。
让我们用Apache HTTPComponent client端插件和Tomcat7 server插件来演示一下。
Apache HTTPComponent客户端插件
span=ContextManager.createExitSpan("/span/operation/name", contextCarrier, "ip:port");
CarrierItemnext=contextCarrier.items();
while (next.hasNext()) {
next=next.next();
httpRequest.setHeader(next.getHeadKey(), next.getHeadValue());
}
Tomcat 7 服务端插件
ContextCarriercontextCarrier=newContextCarrier();
CarrierItemnext=contextCarrier.items();
while (next.hasNext()) {
next=next.next();
next.setHeadValue(request.getHeader(next.getHeadKey()));
}
span=ContextManager.createEntrySpan(“/span/operation/name”, contextCarrier);
ContextSnapshot(上下文快照)
除了跨进程,跨线程也需要得到支持,因为异步执行(内存中的MQ)和批处理在Java中很常见。 跨进程和跨线程是相似的,因为它们都是关于传播上下文。 唯一的区别是,跨线程不需要序列化。
这是跨线程传播的三个步骤:
1.使用ContextManager#capture
获取ContextSnapshot
对象。2.让子线程通过方法参数或由现有参数携带的任何方式访问ContextSnapshot
。3.在子线程中使用ContextManager#continued
。
Core APIs(核心API)
ContextManager
ContextManager
提供了所有主要的和几本的API。
创建
EntrySpan
publicstaticAbstractSpancreateEntrySpan(StringendpointName, ContextCarriercarrier)
创建EntrySpan通过操作名(比如服务名、URI等) 和 ContextCarrier
创建
LocalSpan
publicstaticAbstractSpancreateLocalSpan(StringendpointName)
通过操作名称(比如方法的全限定名等)创建LoalSpan
创建
ExitSpan
publicstaticAbstractSpancreateExitSpan(StringendpointName, ContextCarriercarrier, StringremotePeer)
通过操作名(比如服务名、URI等)、new一个ContextCarrier 地址信息(比如ip+port,或者hostname+port)和创建ExitSpan
AbstractSpan
/*** Set the component id, which defines in {@link ComponentsDefine}
*
* @param component
* @return the span for chaining.
*/
AbstractSpansetComponent(Componentcomponent);
/**
* Only use this method in explicit instrumentation, like opentracing-skywalking-bridge.
* It it higher recommend don"t use this for performance consideration.
*
* @param componentName
* @return the span for chaining.
*/
AbstractSpansetComponent(StringcomponentName);
AbstractSpansetLayer(SpanLayerlayer);
/**
* Set a key:value tag on the Span.
*
* @return this Span instance, for chaining
*/
AbstractSpantag(Stringkey, Stringvalue);
/**
* Record an exception event of the current walltime timestamp.
*
* @param t any subclass of {@link Throwable}, which occurs in this span.
* @return the Span, for chaining
*/
AbstractSpanlog(Throwablet);
AbstractSpanerrorOccurred();
/**
* Record an event at a specific timestamp.
*
* @param timestamp The explicit timestamp for the log record.
* @param event the events
* @return the Span, for chaining
*/
AbstractSpanlog(longtimestamp, Map<String, ?>event);
/**
* Sets the string name for the logical operation this span represents.
*
* @return this Span instance, for chaining
*/
AbstractSpansetOperationName(StringendpointName);
除了operation name
、tags
、logs
还有两个属性需要设置,即component
和layer
SpanLayer
是span的一种,有五种类型:
UNKNOWN (default)
DB
RPC_FRAMEWORK(RPC框架,不是通常的HTTP)
HTTP
MQ
Component IDs被skywalking项目定义和保留。
对于component name/ID扩展,请遵循 Component library definition and extension 文档。
Advanced APIs
Async Span APIs(异步Span API)
Span中有一组高级API,这些API专用于异步方案。 当span的标签,日志,属性(包括结束时间)需要在另一个线程中设置,则应使用这些API。
/*** The span finish at current tracing context, but the current span is still alive, until {@link #asyncFinish}
* called.
*
* This method must be called<br/>
* 1. In original thread(tracing context).
* 2. Current span is active span.
*
* During alive, tags, logs and attributes of the span could be changed, in any thread.
*
* The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match.
*
* @return the current span
*/
AbstractSpanprepareForAsync();
/**
* Notify the span, it could be finished.
*
* The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match.
*
* @return the current span
*/
AbstractSpanasyncFinish();
在原始上下文中调用
#prepareForAsync
。完成当前线程中的工作后,在原始上下文中执行
ContextManager#stopSpan
。将跨度传播到任何其他线程。
完成所有设置后,在任何线程中调用
#asyncFinish
。跟踪上下文将完成,并在所有跨度的
#prepareForAsynsc
完成时向后端报告(由API执行次数判断)。
Develop a plugin(开发插件)
Abstract
追踪的几本方法是使用字节码技术或者AOP来拦截Java方法。
Skywalking封装了字节码操作并追踪上下午传播,所以你只需要定义拦截点(在Spring中也称为切入点)即可。
Intercept
Skywalking提供了两种通用的拦截构造函数的方法:实例方法和类方法。
扩展
ClassInstanceMethodsEnhancePluginDefine
类,定义Constructor
拦截点和instance method
拦截点。扩展
ClassStaticMethodsEnhancePluginDefine
定义类方法拦截点。
当然,你也可以扩展ClassEnhancePluginDefine
,来设置所有的拦截点,但是一般不这么用。
Implement plugin
下面演示如何通过扩展ClassInstanceMethodsEnhancePluginDefine
实现一个插件。
定义目标类名称
protectedabstractClassMatchenhanceClass();
ClassMatch
表示如何匹配目标类,有四种方式:
byName
,通过类的全限定名称(包名
+.
+类名
)byClassAnnotationMath
,通过类存在的特定注解byMethodAnnotationMatch
,通过类方法存在的特定注解byHierarchyMatch
,通过类的父类或者接口
注意:
在增强定义中,永远不要使用
ThirdPartyClass.class
,比如takesArguments(ThirdPartyClass.class)
,或者byName(ThirdPartyClass.class.getName())
,因为在目标应用中并不一定存在ThirdPartyClass,且这会破坏Agent
。 我们在CI
中有import
检查来帮助校验,但是它并不涵盖此限制的所有情况,因此切勿尝试通过使用完全限定的类名(FQCN)之类的方法来解决此限制,例如:takesArguments(full.qualified.ThirdPartyClass.class)
和byName(full.qualified.ThirdPartyClass.class.getName())
将通过CI检查,但是在agent的代码中仍然无效,请使类的全限定名。
即使你完全确定要拦截的类也存在于目标应用程序中(例如JDK的类),仍然不要使用
*.class.getName()
来获取类的String名称,建议使符串。 这是为了避免ClassLoader带来的问题。by*AnnotationMatch
不支持继承的注解不推荐使用
byHierarchyMatch
,除非必须要用的时候。因为可能会触发拦截许多不是自己想拦截的方法,这会导致性能问题。
例如:
@OverrideprotectedClassMatchenhanceClassName() {
returnbyName("org.apache.catalina.core.StandardEngineValve");
}
定义一个实例方法的拦截点:
publicInstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints();
publicinterfaceInstanceMethodsInterceptPoint {
/**
* class instance methods matcher.
*
* @return methods matcher
*/
ElementMatcher<MethodDescription>getMethodsMatcher();
/**
* @return represents a class name, the class instance must instanceof InstanceMethodsAroundInterceptor.
*/
StringgetMethodsInterceptor();
booleanisOverrideArgs();
}
还可以使用Matcher
设置目标方法。 如果想要在拦截器中修改参数引用,则需要在isOverrideArgs
中返回true。
以下各章节将讲述如何实现拦截器。
3.将插件定义添加到skywalking-plugin.def文件中
tomcat-7.x/8.x=TomcatInstrumentation
Implement an interceptor
实现一个实例方法拦截器,需要实现接口org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor
/*** A interceptor, which intercept method"s invocation. The target methods will be defined in {@link
* ClassEnhancePluginDefine}"s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine}
*
* @author wusheng
*/
publicinterfaceInstanceMethodsAroundInterceptor {
/**
* called before target method invocation.
*
* @param result change this result, if you want to truncate the method.
* @throws Throwable
*/
voidbeforeMethod(EnhancedInstanceobjInst, Methodmethod, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResultresult) throwsThrowable;
/**
* called after target method invocation. Even method"s invocation triggers an exception.
*
* @param ret the method"s original return value.
* @return the method"s actual return value.
* @throws Throwable
*/
ObjectafterMethod(EnhancedInstanceobjInst, Methodmethod, Object[] allArguments, Class<?>[] argumentsTypes,
Objectret) throwsThrowable;
/**
* called when occur exception.
*
* @param t the exception occur.
*/
voidhandleMethodException(EnhancedInstanceobjInst, Methodmethod, Object[] allArguments, Class<?>[] argumentsTypes,
Throwablet);
}
在before
、after
和exception
环境使用这些核心API
启动类增强机制
SkyWalking已将引导程序方法打包在agent-core
里面。 通过在Instrumentation
定义,很容易启用。
重写方法 public boolean isBootstrapInstrumentation()
且返回true,如下所示:
publicclassURLInstrumentationextendsClassEnhancePluginDefine {privatestaticStringCLASS_NAME="java.net.URL";
@OverrideprotectedClassMatchenhanceClass() {
returnbyName(CLASS_NAME);
}
@OverridepublicConstructorInterceptPoint[] getConstructorsInterceptPoints() {
returnnewConstructorInterceptPoint[] {
newConstructorInterceptPoint() {
@OverridepublicElementMatcher<MethodDescription>getConstructorMatcher() {
returnany();
}
@OverridepublicStringgetConstructorInterceptor() {
return"org.apache.skywalking.apm.plugin.jre.httpurlconnection.Interceptor2";
}
}
};
}
@OverridepublicInstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
returnnewInstanceMethodsInterceptPoint[0];
}
@OverridepublicStaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
returnnewStaticMethodsInterceptPoint[0];
}
@OverridepublicbooleanisBootstrapInstrumentation() {
returntrue;
}
}
注意,仅在必要时进行引导检测,但大多数情况下会影响JRE core(rt.jar),并可能意料之外的结果和副作用。
贡献插件到Apache SkyWalking仓库
我们欢迎大家贡献插件。
请按照以下步骤操作:
提交一个有关您要贡献哪些插件的问题,包括支持的版本。
在
apm-sniffer/apm-sdk-plugin
或者apm-sniffer/optional-plugins
模块下创建自模块,插件项目的名称需要包含支持的库的名称和版本按照本指南进行开发。 确保提供了注释和测试用例。
开发和测试。
提供自动测试用例。如何编写插件测试用例,可以参考此文档。
发送pr并申请review
插件提交者审批通过提交的插件,插件CI-with-IT,e2e和插件测试通过。
SkyWalking接受插件。
以上是 Skywalking插件开发指南 的全部内容, 来源链接: utcz.com/z/513414.html