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

字节增强条件匹配

在 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. Razor字符串处理

    需要注意的是低版本是不支持C# 6语法中的string interpolation的 <label> @if (!string.IsNullOrEmpty(Model.BudgetValu ...

  2. opencv_traincascade级联训练人脸数据

    正负样本格式:  正样本 灰度化 24*24 2000张  负样本 灰度化 50*50 1000张 训练过程  第一步:dir /b >pos.txt 以及dir /b >neg.txt ...

  3. seaweedfs文件存储服务器搭建

    官方网站: https://github.com/chrislusf/seaweedfs/wiki/Getting-Started 概述 seaweedfs是一个非常优秀的由 golang 开发的分布 ...

  4. C11中的Unicode

    在C11(ISO/IEC 9899:2011)标准中引入了对UTF8.UTF16以及UTF32字符编码的支持. 其中,UTF8字符直接通过char来定义,字面量前缀使用u8.比如: char c = ...

  5. 阿里RDS数据库 全量备份恢复到本地MYSQL

    阿里RDS数据库 全量备份恢复到本地MYSQL   1.首先下载RDS的全量备份 下载完成后上传到服务器备用   2.安装MySQL 5.6 首先删除机上其他的MySQL版本 检查系统是否安装其他版本 ...

  6. PAT 甲级 1027 Colors in Mars (20 分)(简单,进制转换)

    1027 Colors in Mars (20 分)   People in Mars represent the colors in their computers in a similar way ...

  7. Spring Boot自定义Mapper的SQL语句

    代码如下: 先创建一个Provider类: public class RptEbankFsymtTranflowingProvider { public String select(String or ...

  8. Flutter 状态管理 flutter_Provide

    项目的商品类别页面将大量的出现类和类中间的状态变化,这就需要状态管理.现在Flutter的状态管理方案很多,redux.bloc.state.Provide. Scoped Model : 最早的状态 ...

  9. Eureka启动报错:Failed to load property source from location 'classpath:/application.yml'

    原因: 将application.yml添加到classpath时, 由于变更了application.yml的编码格式(或许也改变了些代码内容), 而target内的yml文件没有实时更新, 从而导 ...

  10. [PyTorch] Facebook Research - Mask R-CNN Benchmark 的安装与测试

    Github项目链接:https://github.com/facebookresearch/maskrcnn-benchmark maskrcnn_benchmark 安装步骤: 安装Anacond ...