第一步skywalking启动的时候skywalking-agent.jar就会运行,会调用skywalking-agent.jar中的pemain方法作为函数的入门
第一步是加载agent.config的配置信息
配置信息的优先级:java -java agent后面携带的参数优先级最高,配置文件夹中的优先级最低
agent.config中对于的配置项对于的配置类是skywalking-agent.jar中的Config类,改Config类中所有的参数都可以在agent.config中进行修改
02-SkyWalking 源码随便看看-插件初始化
上面配置文件的信息加载完成之后,接下来主要进行插件的装载
我们登陆进去
PluginResourcesResolver resolver = new PluginResourcesResolver(); List<URL> resources = resolver.getResources(); if (resources == null || resources.size() == 0) { LOGGER.info("no plugin files (skywalking-plugin.def) found, continue to start application."); return new ArrayList<AbstractClassEnhancePluginDefine>(); } for (URL pluginUrl : resources) { try { PluginCfg.INSTANCE.load(pluginUrl.openStream()); } catch (Throwable t) { LOGGER.error(t, "plugin file [{}] init failure.", pluginUrl); } }
List<URL> resources中保存是agent插件中扫描所用的skywalking-def这个文件
接下来对各个skywalking-plugin.def中的文件进行解析列如dubbo的skywalking-def文件的内容如下
dubbo=org.apache.skywalking.apm.plugin.dubbo.DubboInstrumentation
封装之后name就是为dubbo,defineclass就是dubbo插件进行拦截的类org.apache.skywalking.apm.plugin.dubbo.DubboInstrumentation
接下来我们需要将defineclass实例化,利用反射的机制每一个实例的对象就是一个
AbstractClassEnhancePluginDefine对象
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>(); for (PluginDefine pluginDefine : pluginClassList) { try { LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass()); AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader .getDefault()).newInstance(); plugins.add(plugin); } catch (Throwable t) { LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass()); } } plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault())); return plugins;
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
plugins中保存了我们实例化的插件的对象接下来,我们进行下面的操作
try { pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins()); } catch (AgentPackageNotFoundException ape) { LOGGER.error(ape, "Locate agent.jar failure. Shutting down."); return; } catch (Exception e) { LOGGER.error(e, "SkyWalking agent initialized failure. Shutting down."); return; }
new PluginFinder(new PluginBootstrap().loadPlugins())中传入的参数就是我们已经实例化后的插件集合的对象
public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) { for (AbstractClassEnhancePluginDefine plugin : plugins) { ClassMatch match = plugin.enhanceClass(); if (match == null) { continue; } if (match instanceof NameMatch) { NameMatch nameMatch = (NameMatch) match; LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName()); if (pluginDefines == null) { pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>(); nameMatchDefine.put(nameMatch.getClassName(), pluginDefines); } pluginDefines.add(plugin); } else { signatureMatchDefine.add(plugin); } if (plugin.isBootstrapInstrumentation()) { bootstrapClassMatchDefine.add(plugin); } } }
在代码中取出上面实例类中拦截类的
ClassMatch方法,列如dubbo的
DubboInstrumentation
拦截的dubbo的类就是
org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor,这个类名就是完成的类名
private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter"; private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor"; @Override protected ClassMatch enhanceClass() { return NameMatch.byName(ENHANCE_CLASS); }
PluginFinder的构造函数就是给之前的插件对象进行划分,如果拦截的是实际的想dubbo这种实际的拦截某个类,把插件划分到
Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine对象中
如果拦截的不是某个具体的类就存储到
List<AbstractClassEnhancePluginDefine> signatureMatchDefine 这个集合中
我们来看下
nameMatchDefine的数据结构如下,key就是被拦截的类
org.apache.dubbo.monitor.support.MonitorFilter,value就是
拦截org.apache.dubbo.monitor.support.MonitorFilter类的插件对象,处理
org.apache.dubbo.monitor.support.MonitorFilter这个类可能存在多个拦截器来进行拦截,所以这里是一个list集合
上面的插件弄好了之后,接下来要通过butteboby来拦截上面的的类,如何实现的来看下面的代码
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore( nameStartsWith("net.bytebuddy.") .or(nameStartsWith("org.slf4j.")) .or(nameStartsWith("org.groovy.")) .or(nameContains("javassist")) .or(nameContains(".asm.")) .or(nameContains(".reflectasm.")) .or(nameStartsWith("sun.reflect")) .or(allSkyWalkingAgentExcludeToolkit()) .or(ElementMatchers.isSynthetic()));
上面就是之后ByteBuddy哪些类的字节码不被拦截,接下来要指定哪些类需要被拦截
agentBuilder.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new Listener())
.installOn(instrumentation);
在
pluginFinder.buildMatch()中就去取出需要被buytebody拦截的类,列如dubbo这里取出需要拦截的类就是
org.apache.dubbo.monitor.support.MonitorFilter
new Transformer(pluginFinder)中指定了我们上面产生的插件类,如何对
org.apache.dubbo.monitor.support.MonitorFilter进行拦截处理,我们后面再进行分析
接下来我们来看看skywalking的bootservice的机制
try { ServiceManager.INSTANCE.boot(); } catch (Exception e) { LOGGER.error(e, "Skywalking agent boot failure."); }
我们进入到
ServiceManager方法中我们来进行查看
我们可以看到BootService是一个接口类,skywalking的所用的服务都必须实现这个接口
首先我们可以看到
我们来看看BootService存在哪些实现类,我们来看下
每一个服务的功能都不一样,列如JVMService这个服务就是agent收集到当前机器的jvm信息上传给后端的oap集群的
我们来看下服务的Service方法的具体作用
bootedServices = loadAllServices();
void load(List<BootService> allServices) { for (final BootService bootService : ServiceLoader.load(BootService.class, AgentClassLoader.getDefault())) { allServices.add(bootService); } }
底层利用到了java的spi机制来加载对应的服务
org.apache.skywalking.apm.agent.core.boot.BootService文件的内容如下
上面这些Service就是skywalking agent在启动的时候需要利用spi机制进行扩展进行加载的
private Map<Class, BootService> loadAllServices() { Map<Class, BootService> bootedServices = new LinkedHashMap<>(); List<BootService> allServices = new LinkedList<>(); load(allServices); for (final BootService bootService : allServices) { Class<? extends BootService> bootServiceClass = bootService.getClass(); boolean isDefaultImplementor = bootServiceClass.isAnnotationPresent(DefaultImplementor.class); if (isDefaultImplementor) { if (!bootedServices.containsKey(bootServiceClass)) { bootedServices.put(bootServiceClass, bootService); } else { //ignore the default service } } else { OverrideImplementor overrideImplementor = bootServiceClass.getAnnotation(OverrideImplementor.class); if (overrideImplementor == null) { if (!bootedServices.containsKey(bootServiceClass)) { bootedServices.put(bootServiceClass, bootService); } else { throw new ServiceConflictException("Duplicate service define for :" + bootServiceClass); } } else { Class<? extends BootService> targetService = overrideImplementor.value(); if (bootedServices.containsKey(targetService)) { boolean presentDefault = bootedServices.get(targetService) .getClass() .isAnnotationPresent(DefaultImplementor.class); if (presentDefault) { bootedServices.put(targetService, bootService); } else { throw new ServiceConflictException( "Service " + bootServiceClass + " overrides conflict, " + "exist more than one service want to override :" + targetService); } } else { bootedServices.put(targetService, bootService); } } } } return bootedServices; }
在上面的代码加载service服务的时候,存在下面的一个业务逻辑
这些服务的头上面不是有@OverrideImplementor(SamplingService.class)注解要不就是存在@DefaultImplementor注解
所以在上面的代码加载服务的时候,需要进行下判断
整个业务逻辑如下
加载完成之后的bootedServices的结构如下
key就是加载的service的名称,value就是对应的实例对象
服务加载完成之后,skywalking agent接下来就是调用各个服务的 prepare方法和boot方法
skywalking中的每一个插件对应的父类都是AbstracClassEnhancePluginDefine
skywalking加载插件的时候,会读取插件中国的skywalking-plugin.def文件,skywalking-plugin.def文件中的每一个记录会被封装成为一个PluginDefine对象
上面skywalking-plugin.def文件中定义的右边value的值对应的就是一个AbstracClassEnhancePluginDefine,就是一个插件
上面一共有4个插件
插件的加载就是将所有skywalking plugin中的jar包下面的skywalking-plugin.def文件中定义的value的值,封装到一个list集合中,list集合中的元素就是AbstracClassEnhancePluginDefine
一个AbstracClassEnhancePluginDefine对象就是对应一个skywalking的插件
接下来需要对加载的插件进行分类,一类是按照全类名对字节码进行修改,修改dubbo的插件拦截的是MonitorFilter这个类
还有一种是安装条件。继承关系的插件,改ClassMatch存在很多子类
上面就是对插件进行分类,NameMatch表示全类名拦截插件
skywalking agent启动的时候采用的是插件的架构,所有要被skywalking启动的服务都必须集成bootservice,上面就是启动skywalking插件的所有的服务
加载serviceManger加载所有loadallservice的服务的时候,主要做下面的三件事情
当服务被加载完成之后,接下来就在一个死循环中不断调用服务的生命周期的方法
这里注册了一个jvm的钩子,当skywalking-agent。jar所在的jvm进程退出的时候,需要关闭所有的服务,调用所有服务的shutdown方法
04-Skywalking 源码随便看看-插件体系
接下来我们会重点讲解下,skywalking的插件是如何起作用的
pluginFinder.buildMatch()中就去取出需要被buytebody拦截的类,列如dubbo这里取出需要拦截的类就是
org.apache.dubbo.monitor.support.MonitorFilter
new Transformer(pluginFinder)中指定了我们上面产生的插件类,如何对
org.apache.dubbo.monitor.support.MonitorFilter进行拦截处理,我们后面再进行分析
插件要起作用,核心流程在于上面BuyyteBuudy中指定的Transform这个类
在讲解transformer这个类之前,先介绍下skywalking的插件的结构,我们以dubbo插件为例来进行说明
我们来重点进行讲解org.apache.skywalking.apm.plugin.asf.dubbo.DubboInstrumentation这个拦截点对象,就是用来拦截dubbo的
org.apache.dubbo.monitor.support.MonitorFilter的这个类,
new Transformer(pluginFinder)中指定了我们上面产生的插件类,在方方法中实现DubboInstrumentation类对
MonitorFilter的拦截处理,我们来看下具体的业务流程,我们以dubbo插件为例
在skywalking-plugin.def中定义了插件的拦截点,插件的拦截点默认以instrumentation结尾
拦截点定义之后,需要对拦截点进行拦截,需要用到拦截器
接下来我们来看看dubbbo的拦截点
拦截点主要说明要拦截dubbo的那个类,拦截类的构造方法,还是拦截类的实例成员方法
上面dubbo的拦截点
DubboInstrumentation拦截的是dubbo的
org.apache.dubbo.monitor.support.MonitorFilter这个类,拦截的是
org.apache.dubbo.monitor.support.MonitorFilter这个类的invoke方法,这里拦截的是实例方法,当然也可以拦截静态方法
@Override public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { return new InstanceMethodsInterceptPoint[] { new InstanceMethodsInterceptPoint() { @Override public ElementMatcher<MethodDescription> getMethodsMatcher() { return named("invoke"); } @Override public String getMethodsInterceptor() { return INTERCEPT_CLASS; } @Override public boolean isOverrideArgs() { return false; } } };
这里拦截类的时候可以拦截多个方法,上面仅仅拦截了一个invoke方法,当然也可以拦截多个方法,上面我们也可以看出InstanceMethodsInterceptPoint[]这里拦截的就是对应的实例方法
拦截实例方法有下面的三个配置
这里dubbo拦截点拦截的是类的实例方法或者构造方法,需要集成的类如下
这里dubbo拦截点拦截的是类的静态方法,拦截点的类需要集成的类如下
上面拦截点拦截到dubbo的org.apache.dubbo.monitor.support.MonitorFilter这个类的invoke方法,之后需要拦截器对invoke方法进行处理,这里拦截器就是
private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor";
这里我们重点对DubboInterceptor这个拦截器进行详细的处理
接下来我们来讲解下拦截器
public class DubboInterceptor implements InstanceMethodsAroundInterceptor
DubboInterceptor拦截器实现了InstanceMethodsAroundInterceptor方法
DubboInterceptor拦截器拦截的是实例方法和构造就要实现DubboInterceptor这个方法,拦截静态方法要实现StaticMethodsAroundInterceptor
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance; import java.lang.reflect.Method; /** * A interceptor, which intercept method's invocation. The target methods will be defined in {@link * ClassEnhancePluginDefine}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine} */ public interface InstanceMethodsAroundInterceptor { /** * called before target method invocation. * * @param result change this result, if you want to truncate the method. */ void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable; /** * called after target method invocation. Even method's invocation triggers an exception. * * @param ret the method's original return value. May be null if the method triggers an exception. * @return the method's actual return value. */ Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable; /** * called when occur exception. * * @param t the exception occur. */ void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t); }
这个方法实现类似sprring 的aop,skywalking agent的原理通过字节码增加的方式来实现拦截的功能
例如我们要拦截A.class的invoke方法
我们通过修改字节码的方法来实现,我们修改a.class的字节码文件在调用invoke的方法之前先执行InstanceMethodsAroundInterceptor的beforeMethod方法
在invoke的方法执行完成之后执行InstanceMethodsAroundInterceptor的afterMethod方法,在invoke的方法执行异常之后,执行InstanceMethodsAroundInterceptor的handleMethodException方法
拦截静态方法拦截器也需要实现静态的方法StaticMethodsAroundInterceptor
拦截器拦截构造方法需要实现下面的方法
上面表示拦截了构造方法,构造方法的的参数一共是有2个参数
AbstractStubInterceptor这个类又拦截了构造方法 又拦截了实例方法所以实现了InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor这两个接口
05-Skywalking 源码随便看看-witnessClass 机制
skywalking插件这样解决组件多版本的问题
列如拦截dubbo插件,dubbo存在多个版本,skywalking如何对多个版本的插件进行拦截了
比如dubbo 1.0版本拦截类的方法是invoke方法
但是在dubbo 2.0版本的时候拦截类的方法从invoke方法变成了run方法
插件是如何解决这个问题的了,就是witnessClass机制
skywalking运行项目组编写查询的使用自定义AbstracClassEnhancePluginDefine类下面的witnessClass方法
在该方法中可以指定你需要拦截的版本中特定的类名,skywalking通过witnessClass方法中定义的类名,来判断当前的插件是否适合用于拦截当前的版本
列如当前dubb 2.0.jar版本有个唯一的类叫com.xx.weiyuan 我们在witnessClass中指定类名为叫com.xx.weiyuan,skywalking插件启动的时候通过字节码会加载dubb 2.0.jar,看dubb 2.0.jar中是否存在witnessClass中指定的类,如果存在当前的插件适合拦截改版本
例如spring 存在spring 3 spring 4 spring 5版本
bu接下来我们以spring3为例子,我们来看下spring3的拦截点定义的
public abstract class AbstractSpring3Instrumentation extends ClassInstanceMethodsEnhancePluginDefine { public static final String WITHNESS_CLASSES = "org.springframework.web.servlet.view.xslt.AbstractXsltView"; @Override protected final String[] witnessClasses() { return new String[] {WITHNESS_CLASSES}; }
这个类org.springframework.web.servlet.view.xslt.AbstractXsltView只有在spring3这个jar里面才有,在spring4版本中是不存在的
上面这个类就是在spring3中唯一的,在spring4以上的版本都没有
spring 拦截的是带有RestController的注解进行拦截,拦截的方法是带有GetMapping postmapping的方法
对拦截点对witnessClasses的校验我们在后进行详细的分析
06-Skywalking 源码随便看看-拦截静态方法插件的应用
接下来我们重点讲解下之前的transformer函数,这个函数是整个插件运行的入口
private static class Transformer implements AgentBuilder.Transformer { private PluginFinder pluginFinder; Transformer(PluginFinder pluginFinder) { this.pluginFinder = pluginFinder; } @Override public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder, final TypeDescription typeDescription, final ClassLoader classLoader, final JavaModule module) { List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription); if (pluginDefines.size() > 0) { DynamicType.Builder<?> newBuilder = builder; EnhanceContext context = new EnhanceContext(); for (AbstractClassEnhancePluginDefine define : pluginDefines) { DynamicType.Builder<?> possibleNewBuilder = define.define( typeDescription, newBuilder, classLoader, context); if (possibleNewBuilder != null) { newBuilder = possibleNewBuilder; } } if (context.isEnhanced()) { LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName()); } return newBuilder; } LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName()); return builder; } }
我们来看下代码的具体实践
Transformer implements AgentBuilder.Transformer
我们来看下 AgentBuilder.Transformer
interface Transformer { /** * Allows for a transformation of a {@link net.bytebuddy.dynamic.DynamicType.Builder}. * * @param builder The dynamic builder to transform. * @param typeDescription The description of the type currently being instrumented. * @param classLoader The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader. * @param module The class's module or {@code null} if the current VM does not support modules. * @return A transformed version of the supplied {@code builder}. */ DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module);
构造方法有4个参数:
builder对象:当前被拦截类的字节码对象org.apache.dubbo.monitor.support.MonitorFilter
typeDescription:就是被拦截的对象typeDescription
classLoader:这个classLoader就是加载对象MonitorFilter的classLoader
我们来看下transformer的实现方法
@Override public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder, final TypeDescription typeDescription, final ClassLoader classLoader, final JavaModule module) { List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription); if (pluginDefines.size() > 0) { DynamicType.Builder<?> newBuilder = builder; EnhanceContext context = new EnhanceContext(); for (AbstractClassEnhancePluginDefine define : pluginDefines) { DynamicType.Builder<?> possibleNewBuilder = define.define( typeDescription, newBuilder, classLoader, context); if (possibleNewBuilder != null) { newBuilder = possibleNewBuilder; } } if (context.isEnhanced()) { LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName()); } return newBuilder; } LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName()); return builder; }
List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);这个方法的作用就是输入的是typeDescription就是被拦截类的对象列如dubbo org.apache.dubbo.monitor.support.MonitorFilter,取出org.apache.dubbo.monitor.support.MonitorFilter拦截org.apache.dubbo.monitor.support.MonitorFilter有哪些拦截点,我们来看看List<AbstractClassEnhancePluginDefine>的数据结构如下
列如这里拦截ch.qos.logback.classic.spi.LoggingEvent这个类,得到的拦截点如下
LogginEventInstrumentation这个拦截点的实例对象
列如拦截阿里巴巴的
得到的
List<AbstractClassEnhancePluginDefine> pluginDefines
的结构如下pluginDefines中保存的就是DubboInstrumentation的一个实例对象
在代码来实现具体的拦截方法
DynamicType.Builder<?> possibleNewBuilder = define.define(
typeDescription, newBuilder, classLoader, context);
在 define.define我们进入到改方法,我们来看下实际的情况
public DynamicType.Builder<?> define(TypeDescription typeDescription, DynamicType.Builder<?> builder, ClassLoader classLoader, EnhanceContext context) throws PluginException { String interceptorDefineClassName = this.getClass().getName(); String transformClassName = typeDescription.getTypeName(); if (StringUtil.isEmpty(transformClassName)) { LOGGER.warn("classname of being intercepted is not defined by {}.", interceptorDefineClassName); return null; } LOGGER.debug("prepare to enhance class {} by {}.", transformClassName, interceptorDefineClassName); /** * find witness classes for enhance class */ String[] witnessClasses = witnessClasses(); if (witnessClasses != null) { for (String witnessClass : witnessClasses) { if (!WitnessClassFinder.INSTANCE.exist(witnessClass, classLoader)) { LOGGER.warn("enhance class {} by plugin {} is not working. Because witness class {} is not existed.", transformClassName, interceptorDefineClassName, witnessClass); return null; } } } /** * find origin class source code for interceptor */ DynamicType.Builder<?> newClassBuilder = this.enhance(typeDescription, builder, classLoader, context); context.initializationStageCompleted(); LOGGER.debug("enhance class {} by {} completely.", transformClassName, interceptorDefineClassName); return newClassBuilder; }
transformClassName就是获得要拦截的类
接下来就是witenness机制的验证,验证当前的拦截点能否拦截transformClassName
这里验证的是classLoader是加载transformClassName的classLoader,在这里classLoader中查询witenessClass是否在改classLoader中存在,如果存在说明拦截点能够拦截改transformClassName
接下来我们来看 DynamicType.Builder<?> newClassBuilder = this.enhance(typeDescription, builder, classLoader, context);
在方方法中实现真正的拦截操作
@Override protected DynamicType.Builder<?> enhance(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader, EnhanceContext context) throws PluginException { newClassBuilder = this.enhanceClass(typeDescription, newClassBuilder, classLoader); newClassBuilder = this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context); return newClassBuilder; }
这里一定要注意在enhance方法中定义了DubboInstrumentation如何拦截org.apache.dubbo.monitor.support.MonitorFilter
真正的拦截操作在运行的时候才实际产生,列如A应用和B应用都是dubbo应用,只有A应用访问B应用发送真正调用的时候才进行真正的拦截器运行操作
enhance方法是skywalking agent在应用启动的时候利用buytebody定义好拦截点和被拦截类的调用关系
在enchance方法中this.enhanceClass(typeDescription, newClassBuilder, classLoader);改方法拦截的是类的静态方法
this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context);是拦截类的实例方法
我们来先看下拦截静态方法
private DynamicType.Builder<?> enhanceClass(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader) throws PluginException { StaticMethodsInterceptPoint[] staticMethodsInterceptPoints = getStaticMethodsInterceptPoints(); String enhanceOriginClassName = typeDescription.getTypeName(); if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0) { return newClassBuilder; } for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) { String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor(); if (StringUtil.isEmpty(interceptor)) { throw new EnhanceException("no StaticMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName); } if (staticMethodsInterceptPoint.isOverrideArgs()) { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(new StaticMethodsInterWithOverrideArgs(interceptor))); } } else { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) .intercept(MethodDelegation.withDefaultConfiguration() .to(new StaticMethodsInter(interceptor))); } } }
在改方法中我们来重点看下拦截的实例方法
private DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader, EnhanceContext context) throws PluginException { ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints(); InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints(); String enhanceOriginClassName = typeDescription.getTypeName(); boolean existedConstructorInterceptPoint = false; if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) { existedConstructorInterceptPoint = true; } boolean existedMethodsInterceptPoints = false; if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) { existedMethodsInterceptPoints = true; } /** * nothing need to be enhanced in class instance, maybe need enhance static methods. */ if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) { return newClassBuilder; }
instanceMethodsInterceptPoints包保存的是拦截实例方法的拦截点对象
enhanceOriginClassName方法就是被拦截的类
@RuntimeType public Object intercept(@Origin Class<?> clazz, @AllArguments Object[] allArguments, @Origin Method method, @SuperCall Callable<?> zuper) throws Throwable { StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz .getClassLoader()); MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); } Object ret = null; try { if (!result.isContinue()) { ret = result._ret(); } else { ret = zuper.call(); } } catch (Throwable t) { try { interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); } throw t; } finally { try { ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); } } return ret; }
我们来看下拦截的实际方法的的几个参数
@Origin Class<?> clazz 被拦截类的字节码 org.apache.dubbo.monitor.support.MonitorFilter字节码对象
@Origin Method method 被拦截的方法invoke方法
@AllArguments Object[] allArguments,被拦截方法的参数
/** * 3. enhance instance methods */ if (existedMethodsInterceptPoints) { for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) { String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor(); if (StringUtil.isEmpty(interceptor)) { throw new EnhanceException("no InstanceMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName); } ElementMatcher.Junction<MethodDescription> junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher()); if (instanceMethodsInterceptPoint instanceof DeclaredInstanceMethodsInterceptPoint) { junction = junction.and(ElementMatchers.<MethodDescription>isDeclaredBy(typeDescription)); } if (instanceMethodsInterceptPoint.isOverrideArgs()) { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .withBinders(Morph.Binder.install(OverrideCallable.class)) .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader))); } } else { if (isBootstrapInstrumentation()) { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); } else { newClassBuilder = newClassBuilder.method(junction) .intercept(MethodDelegation.withDefaultConfiguration() .to(new InstMethodsInter(interceptor, classLoader))); } } } }
在上面的这个方法中实现拦截操作
String interceptor 中保存的就是实际执行操作的拦截器的名称
最后调用到下面的操作
junction对象如下
这里有个很关键的点,对于实例的运行方法
* @param instanceMethodsAroundInterceptorClassName class full name. */ public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); } }
我们上面的intercept对象必须和创建org.apache.dubbo.monitor.support.MonitorFilter这个类的classLoader绑定起来,利用这个classLoader对象创建intercept
相当于直接把intercept这个字节码文件直接注入到了dubbo应用中,intercept运行的时候和MonitorFilter属于同一个进程,这个就可以利用intercept处理MonitorFilter了
所以通过intercept的类名创建intercept对象,必须利用创建MonitorFilter的classLoader来创建intercept对象
public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) {
try {
interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);
} catch (Throwable t) {
throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t);
}
}
对实例方法的拦截也是在类InstMethodsInter中
@RuntimeType public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable<?> zuper, @Origin Method method) throws Throwable { EnhancedInstance targetObject = (EnhancedInstance) obj; MethodInterceptResult result = new MethodInterceptResult(); try { interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); } catch (Throwable t) { LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); } Object ret = null; try { if (!result.isContinue()) { ret = result._ret(); } else { ret = zuper.call(); } } catch (Throwable t) { try { interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); } catch (Throwable t2) { LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); } throw t; } finally { try { ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); } catch (Throwable t) { LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); } } return ret; }
这里我们来重点对上面的几个参数进行详细的分析
@This Object obj 就是被拦截的org.apache.dubbo.monitor.support.MonitorFilter的实例对象
@Origin Method method 就是invoke方法
@AllArguments Object[] allArguments invoke方法对象的参数
我们来看下下面的这个方法的具体实现
public static <T> T load(String className, ClassLoader targetClassLoader) throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException { if (targetClassLoader == null) { targetClassLoader = InterceptorInstanceLoader.class.getClassLoader(); } String instanceKey = className + "_OF_" + targetClassLoader.getClass() .getName() + "@" + Integer.toHexString(targetClassLoader .hashCode()); Object inst = INSTANCE_CACHE.get(instanceKey); if (inst == null) { INSTANCE_LOAD_LOCK.lock(); ClassLoader pluginLoader; try { pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader); if (pluginLoader == null) { pluginLoader = new AgentClassLoader(targetClassLoader); EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader); } } finally { INSTANCE_LOAD_LOCK.unlock(); } inst = Class.forName(className, true, pluginLoader).newInstance(); if (inst != null) { INSTANCE_CACHE.put(instanceKey, inst); } } return (T) inst; }
输入的参数className就是我们拦截器的名称org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor
ClassLoader targetClassLoader 这个是dubbo应用进程创建org.apache.dubbo.monitor.support.MonitorFilter这个进程的classLoader
我们指定拦截器的类位于skywalking agent的目录下
targetClassLoader这个classLoader属于应用进程,利用classLoader创建拦截器是无法创建成功的,因为classLoader属于的应用进程下面是没有拦截器这个类的字节码文件的
拦截器的字节码文件位于skywalking的插件的plugins目录,我们如何targetClassLoader来创建了拦截器类了
我们第一点肯定要让classLoader能够扫描得到plugins目录,这个时候我们创建了一个
pluginLoader = new AgentClassLoader(targetClassLoader);
对象,我们创建了一个AgentClassLoader对象,输入的参数是targetClassLoader
AgentClassLoader这个classLoader是可以加载plugins目录的,DubboInterceptor这个字节码就是位于这个目录下面的apm-dubbo-2.7.x-plugin-8.4.0-SNAPSHOT.jar下面的,我们可以利用AgentClassLoader通过反射将对象创建出来
这里加载类的时候使用到了双亲委派的机制,首先让父类的classLoader去加载,这里父类是dubbo应用启动的classLoader,classLoader在当前的路径下是扫描不到DubboInterceptor这个类所以创建失败,父类创建失败就子类类加载然后由AgentClassLoader来加载,因为AgentClassLoader是可以描述到plugins,所以就能够创建实例成功。
整个DubboInterceptor就创建成功了,这里为啥创建AgentClassLoader为啥需要输入targetClassLoader,就是让DubboInterceptor和targetClassLoader绑定起来
追踪达到的效果等于DubboInterceptor这个字节码对象就属于dubbo这个应用了,启动dubbo应用的时候DubboInterceptor这个对象也和应用一起启动起来了
结论就是:
动态修改字节码对象
接下来我们来看看java的双亲委派机制
java文件会被编译成class文件,而class文件就是通过类加载器classloader进行加载的,java中有BootStrapClassLoader、ExtClassLoader、AppClassLoader三类加载器。
BootStrapClassLoader是使用c++编写的,用于加载java核心类库,是由jvm在启动时创建的,主要是加载JAVA_HOME/jre/lib目录下的类库;
ExtClassLoader用于加载java扩展类库,主要是jre/lib/ext包下的类;
AppClassLoader是应用程序类加载器,用于加载CLASSPATH下我们自己编写的应用程序。
ClassLoader的双亲委派机制是这样的:
当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
作用:双亲委派是为了安全而设计的,假如我们自定义了一个java.lang.Integer类如下,当使用它时,因为双亲委派,会先使用BootStrapClassLoader来进行加载,这样加载的便是jdk的Integer类,而不是自定义的这个,避免因为加载自定义核心类而造成JVM运行错误。
package java.lang;
/**
* hack
*/
public class Integer {
public Integer(int value) {
System.exit(0);
}
}
初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:
public static void main(String... args) {
Integer i = new Integer(1);
System.err.println(i);
}
执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。
整个类的层级结构就变成下面的形式了
方法的第二个参数就是需要被拦截的类,函数的本质就是检查我们的List《AbstracClassEnhancePluginDefine》所有的插件中是否存在一个插件可以拦截需要被拦截的类
此外返回一个newbuilder对象
接下来我们来看看具有的插件是如何拦截需要被拦截的类的流程
一个插件对拦截类的核心拦截流程值在define.define中实现的,改方法的返回值是一个newbuild对象,后续的拦截器需要依据之前的拦截点的返回值继续进行后续的拦截操作
define.define改方法有四个参数,第一个是需要被拦截的类列如dubbo中的monitorfilter,第二个参数是用户当前被拦截类monitorfier的字节码,
改方法中使用witness机制判断当前的插件是否能够拦截需要的点
如果witeness机制通过,然后在调用enhance方法拦截器点需要的拦截方法进行实际的拦截增强
上面enhance中调用了两个方法,一个是调用enhanceClass方法改类主要拦截被拦截类的静态,一个是调用enchanceInstance方法改类主要拦截被拦截类的构造方法和实例方法,我们先来看看enhanceClass方法
首先找的所有静态方法的拦截点
然后遍历全部的拦截点,取出拦截点里面定义的拦截器
接下来要判断拦截器执行拦截操作的时候,是否需要修改原方法执行的入参,分为修改入参和部修改入参两种类型
我们来看下不修改入参的情况
接下来就是执行实际的拦截操作
本质上就是在执行实例被拦截类的静态方法之前,先调用拦截器的beforeMethod方法,在被拦截类的静态方法执行完成之后,再调用拦截器的afterMethod方法
在上面的方法中使用到了大量的bytebuggy的大量注解
接下来我们来看看要修改入参的拦截方法
我们在拦截器的beforeMethod中可以修改原方法的入参,修改测参数存储在allArgument中
07-Skywalking 源码随便看看- 拦截实例方法插件的应用
接下来我们看看对构造方法和实例方法的拦截
在skywalking中存在一个很重要的类InterceptorInstanceLoader,这个类主要是加载拦截器到classLoader中,使用到了双亲委派机制
我们重点了解下这个InterceptorInstanceLoader
改方法需要传入两个参数,一个是拦截器的全类名称,第二个参数就是列如拦截方法,就是拦截该方法的classLoader,返回值就是得到通过拦截器的全类名获得该拦截器的实例对象
接下来这里笔记重要的是AgentClassLoader
改loader只加载skywalking agent插件plugin和activeer下面的jar包,然后从下面的jar包中选择对应的拦截器,然后通过反射将拦截器实例出成对象
我们来看下new AgentClassLoader的时候,传入了一个参数TargentClassLoader,TargentClassLoader就是被拦截类对于的classLoader,对于的加上Monitorfilter'的classLoadre
AgentClassLoader在TargentClassLoader的基础上指定AgentClassLoader只加载
AgentClassLoader的父类就是TargentClassLoader,这样就让拦截器和TargentClassLoader绑定起来了,让TargentClassLoader通过拦截器的全类名将拦截器实例对象创建出来
08-Skywalking 源码随便看看-BootService 之 GRPCChanelManager
skywalking agent需要和后端的oap存在网络连接
GRPCChanelManager这个服务就是负责agent个oap的通信
standardCjannelBuilder中定义了agent和后端通信的时候,默认大学是50M,使用明文的传输
TLSChannelBuilder中定义了agent和后端通信可以采用加密的方式
agent和oap进行通信的时候,如果oap需要对接入的agent进行认证,需要在agent中添加对于的token
认证,如下类AuthenticationDecorator类
GRPCChannelManger是一个服务实现了BootService
我们在skywalking的agent.config中配置了后端oap集群的地址
agent随机的从oap的集群地址中随机选择一个进行连接,如果连接成功不进行任何操作
如何连接失败每隔30秒会进行重连,再从后端oap集群的地址随机选择一个再进行连接,这个地址不能和之前连接失败的地址一样
09-Skywalking 源码随便看看-BootService 之 ServiceAndEndPointRegisterClie
ServiceAndEndPointRegisterClieent这个类也实现了bootserivice的接口,也是一个服务,主要用来进行skywalking服务的注册的
上面有三个概念:
1、serivice就是应用的名称,列如用户应用,就是应用的名称
2、serviceInstance:应用实例,就是应用具体的jvm进程
5、endpoint:就是应用里面具体的接口,例如/getUserList接口
存在两个实例变量,一个是registerBlockingStub,这里是一个远程接口通过netty的grpc框架,底层使用probuffer协议,来进行应用和端口的注册
一个是serviceInstancePingstub这里是一个远程接口通过netty的grpc框架,底层使用probuffer协议,来保证agent和后端oap集群的心跳
public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { try { interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); } catch (Throwable t) { throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); } }
在ServiceAndEndPointRegisterClieent的prepare方法中做了两个事情,第一需要监听agent和后端opa集群通信的channel的状态,监控channel是断开还是成功
第二要生成一个当前实例进程的uuid,后续实例注册的时候需要使用到改uuid
接下来就是通过线程池开启一个定时任务进行serivice的注册。应用实例的注册 以及agent和后端进行心跳的测试
首先我们来看下service应用的注册
service在后端注册成功之后,oap集群会返回一个int的id值,表示当前应用在后端的唯一编号,列如编号1在oap集群后端表示user应用
oap集群返回的唯一编号的值会缓存在本地的RemoteDownstreamConfig缓存中
服务注册的时候需要用到probuffer协议,定义在下面
service注册的时候需要通过bufferbuf协议携带应用的应用名称,应用名称在agent,conf中config.agent.service_name进行指定
注册成功之后,返回值是应用名称在oap集群后端中产生的唯一编号,通过serviceRegisterMapping进行返回
并且把应用的唯一编号放在缓存中
接下来我们来看看serviceInstance实例的注册
实例注册的时候在probuffer中定义了需要携带三个参数
第一个参数是应用的id,就是上面应用注册的时候oap返回的应用的id
第二个参数就是刚刚前面生成的本地实例进程的uuid
接下来oap集群注册成功之后也会给我们返回一个当前实例进程的唯一编号,然后将实例唯一编号缓存起来
接下来我们来看看心跳
把当前应用的唯一编号和实例编号发送给后端的oap集群保持心跳
心跳注册完成之后,接下来还存在一个很关键的业务点,就是数据字典的同步
现在有个redis服务对应的ip和端口为127.0.0.1:6379
order应用会通过get接口访问redis服务,采样的时候会将127.0.0.1:6379这个地址上传给后端,因为redis的ip和端口不会轻易编号,我们可以使用数字1代表redis的ip和端口地址,网络传输的话agent就使用1发送给后端oap集群,agent不再使用127.0.0.1:6379进行网络传输,节约了带宽资源也提高了传输效率,oap集群中维护了1和127.0.0.1:6379这个列表,后期会进行转化
在本地的agent也会缓存一份1和127.0.0.1:6379的映射列表
这里存在一个find方法,列如输入127.0.0.1:6379就会返回对于的编号为1,如果未找到就将127.0.0.1:6379放在未注册的列表中,下一次心跳的时候重新再到后端进行注册
上面完成的ip端口的映射,接下来要讲讲端口接口的映射
有些概念需要注意
端口字段映射需要携带上面的这些信息,应用id,应用端口的名称,当前端口是调出还是调入
当前当前的接口是浏览器访问的入口,那么当前的接口就是作为server,作为被调用方
如果当前的接口是调出远程访问其他接口,当前的接口就是client,访问远程服务
10-Skywalking 源码随便看看-BootService 之 JVMService
jvmservice业务一个服务,用来收集应用实例的jvm信息发送给后端的oap集群
jvm的信息都封装到jvmMetric中
两个定时器,一个收集metric数据,一个发送数据到后端oap中
发送jvm信息的默认有个buffer的大小默认是60*10
收集jvm的进程的代码如下
ServiceAndEndPointRegisterClie
BootService
invoke