大家好,我是烤鸭:
以前写过一篇全链路探针实现的文章,最近同事间搞技术分享,再整理一篇。可惜这两年没有继续搞这方面的技术,算是两年前的拓展篇吧。很多技术只放了图,文字就不写了,可以参考下边的拓展阅读。
文中显示代码的地址:https://gitee.com/fireduck_admin/link-trace-demo
探针的实际使用
最开始接手项目的时候,公司有自己的全链路采集,由于服务端大部分是java项目,所以采集的上报是使用基于拦截的方式(AOP)。
基于拦截其实有比较成功的案例,像pinpoint或者cat。
当时还有基于探针的全链路采集像skywalking,就想着能不能用skywalking的方式(探针)重构下。
重构需要改几百个项目的接入方式,如果没有合适的理由恐怕很难驱动。
于是就想着怎么做一些数据对比,证明探针技术的更有效。
为什么要使用探针技术
其实之前的文章也有写到过,最方便的就是解耦。包含接入和升级都不会有任何的代码侵入,包括java代码或者pom引包。
除此之外呢,我觉得是代码的封闭性。业务不需要关心全链路的采集,甚至看不到任何与之有关的影子,前提是需要做好异常处理和兼容性测试。
当然,数据是少不了的,之前写过一个简略的带数据对比的文章。
https://blog.csdn.net/Angry_Mills/article/details/107868420
归根结底是因为字节码方式不同,性能耗时有区别。
字节码技术介绍
网上有很多的资料可以去看,写的就不那么详细了。
什么是Java字节码,其实就是.class文件。.class文件是由十六进制值组成的,JVM以两个十六进制值为一组,就是以字节为单位进行读取
JVM加载字节码的过程:
加载的过程:以JDK 1.8为例
双亲委托机制:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派到父类加载器来完成,每一个层次都是如此,因此最后的加载请求都是传送到最顶层的启动类加载器,只有当父类加载无法加载这个类时(这个类不是自己的加载范围),子加载器才会尝试自己去完成加载。
总结一句话:子类加载器无法加载父类加载器的类。
字节码框架
用的比较多的几种框架,asm、javasisst、cglib。
asm
官网:https://asm.ow2.io/index.html
idea插件:ASM Bytecode Outline (https://plugins.jetbrains.com/plugin/5918-asm-bytecode-outline)
javasisit
官网:http://www.javassist.org/、http://www.javassist.org/tutorial/tutorial.html
不能操作实例变量和实例方法。
cglib和jdk proxy
官网:https://github.com/cglib/cglib
最常见的面试题,这俩的区别的优劣势。
以一个实际场景来看,分别用这三个框架对一个类的方法进行增强,记录方法调用的耗时。
代码演示:(asm)
代码演示:(javassist)
javaagent 工作原理
JVMTI (jvm interface tool)是 jdk1.2以后加入的,翻译为工具性接口,agentmain和premain,其实就是jvm给开发人员开放了一些api的后门,可以调用一些native的方法。
像我们熟知的idea工具很多地方也是基于探针实现的,启动项目看一下就知道了。
D:\dev\env\jdk\jdk1.8\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63992,suspend=y,server=n -javaagent:C:\Users\烤鸭的世界\AppData\Local\JetBrains\IntelliJIdea2021.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "D:\dev\env\jdk\jdk1.8\jre\lib\xxx.jar" com.maggie.measure.bytecode.javassist.Javassist
JPLISAgent(Java Programming Language Instrumentation Services Agent)
拓展阅读
class文件官方说明
彻底让你搞懂什么是Java字节码
jvm 加载字节码过程
使用ASM操作Java字节码,实现AOP原理
美团-Java字节码增强探秘
NoClassDefFoundError排查
JPDA学习