第一步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