整理自架构经理(汤哥)的分享

字节增强条件匹配

在 skywalking 中实现很多基于 byte-buddy 的关于链式匹配查询的实现, 代码如下所示:

public abstract class AbstractJunction<V> implements ElementMatcher.Junction<V>

其对应的类的类图关系如下所示:

除此之外, 为了便于更好的执行在拦截时期的的匹配, skywalking  又定义了一套自实现的 Match  的匹配器, 类的简要类图关系如下所示:

其中 NameMatch 名称 Match 的 NameMatch 好象相对类的继承相关较独立,但是其它方式的 Match 继承结构相对有点复杂, 都继承于IndirectMatch , 上面类的简意类图对应的类的完整类图详情如下所示:

插件方式增强的扩展点

所有的基于 skywalking 的整套增强逻辑框架下,如查有新的要增强的中间件或组件, 必须遵守其制定的插件扩展规范, 其具体的规范如下:

一、 在对应的插件的 jar 包的 classpath 下必须存在定义文件 skywalking-plugin.def ,文件中必须包括 对 JTI 中的 Instrumentation 的自定义类的全限定名, 以 Dubbo 增强为例, 其内容为

dubbo=org.apache.skywalking.apm.plugin.dubbo.DubboInstrumentation

二、自定义的 Instrumentation 必须继承至 ClassInstanceMethodsEnhancePluginDefine 抽象类 , 在类中,须指定这个设施的增强的两个核心要素内容:

A). 须要增强的目标类 , Enhance Class
B). 插件中自己要实现的具体的拦截实现 xxxxxInterceptor
同样以 Dubbo 为例, 如果要自定义自己的插件实的话,则就要指定如下内容:

三、最后就是要真实的定义一个 实现接口 InstanceMethodsAroundInterceptor  的一个拦截实现

对于每一个插件来说, 不一定只能有一个 Interceptor , 还可以有多个, 因为有时不一定只是要对一个类或一个方法进行拦截, 可能是多个类, 也有可能是一个类中的多个方法, 或多个类的多个方法,因此,在最初的 Instrumentation 入口定义之外,就提供了可以多个方法的增强扩展, 具体的如下图针对 Dubbo 的增强所示:

插件方式增强主体逻辑

理解实现插件增强机理的类与类之间的关系,犹为重要,它也是 sky-walking 的对于中间件以插件方式进行增强操作的核心, 下面我们就来仔细的分析下,是如何进行自定义插件, 并如何进行对中间件的增强的。在整个的增强的机制中, ClassEnhancePluginDefine 是很重要, 很核心的一个类, 在其 enhance 方法中, 分别指定了对

  • 静态方法增强
  • 实现方法增强
  • 定义了 ConstructorInterceptPoint、InstanceMethodsInterceptPoint、StaticMethodsInterceptPoint

这三个 构造器,实例方法, 静态方法拦截点的 抽象 方法, 让插件子类去实现拦截点

下面是在 Dubbo  为例 DubboInstrumentation  插件主体实现依赖的类图关系:

上面的是以 ClassEnhancePluginDefine 为核心的主体的核心类图结构, 现在主要讲一下此类的内部的主要构成与执行序列, 完整的类图如下所示:

从上面可知, ClassEnhancePluginDefine 继承至 AbstractClassEnhancePluginDefine , 做为所有的插件都要继承实现的基础, 抽象类AbstractClassEnhancePluginDefine 具体定义了什么行为, 这些行为又有哪些意义?什么地方在调用这个方法?

AbstractClassEnhancePluginDefine 中有四个方法

  • enhance 抽象方法, 抽象类中无实现, 子类去实现,具体的增强的逻辑
  • enhanceClass 抽象方法, 抽象类中无实现,子类去实现,具体的要增强的类的 Match
  • witnessClasses 也是可以被重载的一个方法,子类可重载
  • define 实例方法,它主要做了下面的几次事 ( 入口方法)

1 找到增强插件类的所有显式指定的 WitnessClasses - 用户自己重载显式指定
2 调用 enhance 方法, 执行真正的插件的增强逻辑,返回新的 DynamicType.Builder
3 设置上下文, 标记初始化定义步骤已完成
4 最后再返回新的 第 2 中的增强后的 newClassBuilder

那么在这个类中,最为重要的一个方法 define 又是谁调用的呢?

上面的就是这个类方法被使用的 Usages 情况, 主要是被 Sky-Walking Agent 的 Agent 主入口调用, 这个方法也是使用 byte-buddy 的字节码增强的 agent 中的一个使用范式, 也就是说在 agent 执行真正的 transformer , 所有的插件中因为都是继承至AbstractClassEnhancePluginDefine , 自然它们的 define 的定义的初始化方法, 也就会被全部调用初始化, 代码如下所示:

对于 snow-walking  来说, 使用 Agent  的固定范式( 来自于 byte-buddy  的固定实现方式)如下所示:

至此,已经分析完了, 所有 sky-walking  的自定义插件都必须要继承并重载的抽象类 AbstractClassEnhancePluginDefine  , 它定义了所有的Agent 插件都必须要实现的行为, 以及它自身需要定义与初始化的行为逻辑。

接下为, 真正的主角上场了, 它就是 ClassEnhancePluginDefine  ,  其类的依赖关系如下:

下面看一下 类的本身的方法, 总共有 六 个方法:

enhance  方法, 重载了父类抽象类 AbstractClassEnhancePluginDefine  的方法, 做为所有插件的基类, 它定义了 enhance  方法必须所具备的操作。此方法的内部主要做了两件事:

  • enhanceClass , 增强一个类,以拦截其静态方法
  • enhanceInstance ,增强一个类,以拦截其构造器 与 实例方法

除了重载的方法外, 其它的三个方法, 分别提供了对应的拦截点:

在实现自定义插件逻辑的时候, 要重载这三个方法中的实现, 以 Dubbo 插件拦截扩展为例, 在 DubboInstrumentation  中 就重载了 父类的getInstanceMethodsInterceptPoints方法, 以此来告诉父类中的共性 enhance  增强处理逻辑, 本插件的实例方法拦截点的一些元数据信, 区配的方法是什么,要增强的类是哪一个,是否要重载其方法参数,这些信息。

上面这里, 在具体的 ClassEnhancePluginDefine  实现插件内部, 对于拦截器 intercepter  都是指定的是 字符串 String  类型, 那么父类中ClassEnhancePluginDefine 是如何转化,并在什么时机调用这个 intercepter 的实现类, 以执行拦截的真实的逻辑的呢?

那上面的这里从用户自定义的插件中显式的指定了 intercepter Fullname了,到了ClassEnhancePluginDefine后,进行enhance时,会最终都落在 byte-buddy  的 builder. method (....).intercpet (MethodDelegation.xxxx)

这样的固定增强的编程范式当中去。在 sky-walking 的实现中, 它又将 intercepter 的名称, 根据不同的增强类型,传入了不同类型的具体的委托执行实例当中去执行了。在 sky-walking 中, 根据增强的分类类型,委托执行实例分为以下几种:

它们分别是, 静态方法固定拦截委托实现器,实例方法固定拦截委托实现器,构造器固定拦截委托实现器, 它们只有一个主要的核心执行方法, 就是 intercept , 这个方法在执行时是与 byte-budy 的 MethodDelegation 配套使用的, 此方法必须要显式的按规定指定相应的byte-buddy 的 Annotation 方能正常的执行增强工作。

因为这三个固定类型的拦截器的处理方式都是差不多, 这里就以 静态方法拦截器举例分析其内部的执行原理。先看一下这个拦截器的类
的依赖情况:

从上面的此拦截器的依赖情况可以, 它主要依赖两个:

  • StaticMethodsAroundInterceptor , 这个接口的实现者是自定义插件自行根据需求实现的
  • InterceptorInstanceLoader ,这个是用户自定义插件的拦截器的类加载器

之前我们说, 执行 byte-buddy 固定的 intercept 逻辑范式时, 通过 MethodDelegation 委托给了 sky-walking 的预设定的几个类型的拦截器,在构建这些固定拦截器时, 传入的都是用户自定义的拦截器的 ClassFullName , 所以在真实的固定类型的拦截器内部,就得有一个机制去加载用户自定义的拦截器,只有这样, 这些拦截器才能被调用执行。
这些拦截器有三个固定的方法:

  • beforeMethod , 被拦截方法之前执行
  • afterMethod ,被拦截方法之后执行
  • handleMethodException ,处理捕获执行期间的异常

OK , 分析到这里, 基本上, 用户自定义增强插件,如何被增强,如何被拦截执行的过程应该算是比较清楚了。

增强与拦截机制总结

下图是整理后的关于 sky-walking  的 APM 中用户自定义插件的完整的调用链路, 它对于插件如何生效并进行增强与拦截的调用过程做了描绘。

Skywalking的增强与拦截机制的更多相关文章

  1. Spring拦截机制之后端国际化心得

    需求 前端请求的header里带有Prefer_Lang参数,向后端传递国际化信息,后端需要在处理业务之前(建立拦截机制),将Prefer_Lang保存于线程上下文. 思路分析 初次接收该需求时,为了 ...

  2. 讲讲Android事件拦截机制

    简介 什么是触摸事件?顾名思义,触摸事件就是捕获触摸屏幕后产生的事件.当点击一个按钮时,通常会产生两个或者三个事件--按钮按下,这是事件一,如果滑动几下,这是事件二,当手抬起,这是事件三.所以在And ...

  3. Android 事件拦截机制一种粗鄙的解释

    对于Android事件拦截机制,相信对于大多数Android初学者是一个抓耳挠腮难于理解的问题.其实理解这个问题并不困难. 首先,你的明白事件拦截机制到底是怎么一回事?这里说的事件拦截机制,指的是对触 ...

  4. 如何绕过chrome的弹窗拦截机制

    如何绕过chrome的弹窗拦截机制 在chrome的安全机制里面,非用户触发的window.open方法,是会被拦截的.举个例子: var btn = $('#btn'); btn.click(fun ...

  5. Android开发系列之事件拦截机制

    对于Android开发者来说理解事件传递机制的重要性,我想应该是不言而喻的.在一个Activity里面,我们经常会重写onTouchEvent事件,可是重写结束之后,对于是返回true还是返回fals ...

  6. Android事件拦截机制简单分析

    前一阶段,在学习的时候,遇到了我觉得的我接触安卓以来的最多的一次事件拦截出来,那个项目,用到了slidemenu側滑菜单条,然后加上tab标签,还有轮播广告,listview上下滑动.viewpage ...

  7. 使用方法拦截机制在不修改原逻辑基础上为 spring MVC 工程添加 Redis 缓存

    首先,相关文件:链接: https://pan.baidu.com/s/1H-D2M4RfXWnKzNLmsbqiQQ 密码: 5dzk 文件说明: redis-2.4.5-win32-win64.z ...

  8. 绕过chrome的弹窗拦截机制

    在chrome的安全机制里面,非用户触发的window.open方法,是会被拦截的.举个例子: var btn = $('#btn'); btn.click(function () { //不会被拦截 ...

  9. View的事件拦截机制浅析

    为什么要去分析view的事件 记得上周刚立的flag就是关于view的事件机制.那现在我来说说我对view的感受.关于view的事件,百度google一搜.一批又一批.但是能让人理解的少之又少.换句话 ...

随机推荐

  1. linux下添加动态链接库路径、动态库加载等方法

    linux下添加动态链接库路径的方法 2017年01月20日 10:08:17 阅读数:5596   Linux共享库路径配置 Linux下找不到共享库文件的典型现象为明明已经安装某个软包(如libn ...

  2. Mac 上卸载node和npm

    Mac 上卸载node和npm 卸载node依次在终端执行下面的脚本 sudo npm uninstall npm -gsudo rm -rf /usr/local/lib/node /usr/loc ...

  3. kafka和rabbitmq对比

    1.吞吐量kafka吞吐量更高:1)Zero Copy机制,内核copy数据直接copy到网络设备,不必经过内核到用户再到内核的copy,减小了copy次数和上下文切换次数,大大提高了效率.2)磁盘顺 ...

  4. 强悍的Python读取大文件的解决方案

    这是一道著名的 Python 面试题,考察的问题是,Python 读取大文件和一般规模的文件时的区别,也即哪些接口不适合读取大文件. 1. read() 接口的问题 f =open(filename, ...

  5. Servlet 的三种跳转方式

    1.Forword request.getRequestDispatcher (“url”).forword (request,response) 是请求转发,也就是说,一个 Servlet 向当前的 ...

  6. java.lang.NoClassDefFoundError: org/springframework/aop/TargetSource

    在使用Spring框架时 报错 :java.lang.NoClassDefFoundError: org/springframework/aop/TargetSource 原因:为引入spring-a ...

  7. []how to use caffe model with TensorRT c++

    //IHostMemory *gieModelStream {nullptr}; //const char* prototxt = "./googlenet/test_20181010.pr ...

  8. python基础--字符串、元组

    字符串方法 注:字符串和元组一样都是不可变的,以下函数不会改变原来字符串的值,可以将其赋给一个新的变量 st='a b c d,f,g' st_list=st.split()#字符串分割,参数不写,按 ...

  9. OpenCV.用户选择

    1.Pdf.P153(书.P122) 2. // 来自:"Creating a video with OpenCV — OpenCV 2.4.13.7 documentation.html& ...

  10. C#实现工作日和休息日(包括法定节假日)的计算

    转自:http://www.cnblogs.com/yuan-chong/p/HolidayHelper.html 一.开发背景: 最近在公司开发的系统中,需要计算工作日,就是给出一个采购周期(n天) ...