调用链的兴起
- 分布式遇到的问题:随着微服务设计理念在系统中的应用,业务的调用链越来越复杂。一个请求可能会涉及到几十个服务的协同操作,涉及到多个团队的业务系统。当遇到问题需要定位时候,也会产生一系列的麻烦。
- 解决方案:通过调用连,把一次请求调用过程完整的串联起来,实现了对请求调用路径的监控,便于故障快速定位。
- 调用链显示内容:各个调用环节的性能分析(如各个API使用时间、使用堆栈情况)、在调用连各个环节依赖关系还原、SQL语句打印、IP显示等。
各大公司的调用链框架
- Google: Dapper
- 淘宝 鹰眼
- 京东 hyfra(九头蛇) 希腊神话中的一种,京东开发平台是宙斯,基于dubbo来实现。
调用链原理
- 请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。
- 除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下Parent id和Span id,通过他们可以组织一次完整调用链的父子关系。
- 一个没有Parent id的span成为root span,可以看成调用链入口。
- 所有这些ID可用全局唯一的64位整数表示;
- 整个调用过程中每个请求都要透传TraceID和SpanID。
- 每个服务将该次请求附带的TraceID和附带的SpanID作为Parent id记录下,并且将自己生成的SpanID也记录下。
- 要查看某次完整的调用则只要根据TraceID查出所有调用记录,然后通过Parent id和Span id组织起整个调用父子关系。
调用链实现技术
采用字节码插桩方式,监听JVM虚拟机。好处是对代码侵入为零。
首先需要了解JVM的classLoader加载机制。
- JVM的classLoader主要分为两类,一种是初始加载器,用来加载lib下的所有后缀为.jar的文件。另一种是所有其他类型的加载器,包括自定义的class Loader.
- 加载机制采用双亲委派模式,就是所有的加载器需要加载的时候都会先交给父类去加载,只有当父类加载不了,才会使用自身的加载功能,如此一来,所有的加载都绕回了初始加载器。
- 例如object类定义在rt.jar中,所有的对象都是Object,都需要先加载Object类,进而都需要加载rt.jar文件,进而需要使用初始加载器来加载。
Javassist
- Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而 且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成。
- 作用:AOP动态代理。获取访问类结构信息,如参数名称等。运行时监控插桩埋点。
- 使用流程:
- 构建ClassPool对象,找到要插入类的路径,insertClassPath().
- 获取CTclass, 找到已存在的类get,构建新类makeClass
- 修改构造CTclass,添加属性 addField,添加修改方法addMethod().
- 将修改完成的class生成新的class toByteCode(),装载该class toClass
多级嵌套事件
埋点就是事件捕获,
字节码插桩用到的技术:
javaagent 代理拦截 插桩的入口
javassit字节码修改工具
java.java-->编译-->class文件--->JVM--->字节码
直接写JVM 指令码
JVM ClassLoader加载顺序
1.整体采用双亲委派模式,从下往上开始依次check,如果父类存在就加载。所以最终加载顺序如下:
- Bootstrap ClassLoader,引导类 , Jre_home下lib目录下的几个jar随着JVM的启动器先加载。就类似于下图(1)。例如rt.jar,里面存的是Object
- Ext ClassLoader 扩展类,Jre_home/lib/ext目录下开始加载。就类似于下图(2)。他们的启动都依赖于图1.
- App ClassLoader ,类似于下图(3),容器基础类,会加载容器的bin目录,一般只加载两个jar.
- Common ClassLoader 类似于图(4),容器类
- Share ClassLoader ,容器下项目通用的类,
- WebApp ClassLoader 加载 项目/WEB-INF/lib下的jar,然后在加载classes/下的代码。项目自定义类。
图(1)
图(2)
- 图(3)图(4)
在web项目中,自定义的jar包怎么启动
自己打好的jar包怎么启动:
在JVM参数中 -javaagent: 目录/xx.jar。这种方式会将该jar加载到相当于图3的位置。
Servlet 的jar加载是在图4的位置。
这样就涉及到一个问题,自定义的字节码插装类,会早于servlet的类加载,会出现class no found。
解决方案:
改变加载顺序,将自定义的DispatcherServletCollecct 类,填充到Common ClassLoader层级,让其跟servlet处于同一层级。
pool.get("com.bit.javassist.DispatcherServletCollecct").toClass(loader,null);
这样,DispatcherServletCollecct会同时出现在图4与图3的位置。
http协议会加载图4位置的类,而不会去找图3的,因为 tomcat的加载机制与JDK加载机制不同,tomcat当看到图3中出现了该类,就不管了,不会继续向上请求加载。