分布式调用链(一)

调用链的兴起

  1. 分布式遇到的问题:随着微服务设计理念在系统中的应用,业务的调用链越来越复杂。一个请求可能会涉及到几十个服务的协同操作,涉及到多个团队的业务系统。当遇到问题需要定位时候,也会产生一系列的麻烦。
  2. 解决方案:通过调用连,把一次请求调用过程完整的串联起来,实现了对请求调用路径的监控,便于故障快速定位。
  3. 调用链显示内容:各个调用环节的性能分析(如各个API使用时间、使用堆栈情况)、在调用连各个环节依赖关系还原、SQL语句打印、IP显示等。

各大公司的调用链框架

  1. Google: Dapper
  2. 淘宝 鹰眼
  3. 京东 hyfra(九头蛇) 希腊神话中的一种,京东开发平台是宙斯,基于dubbo来实现。

调用链原理

  1. 请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。
  2. 除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下Parent id和Span id,通过他们可以组织一次完整调用链的父子关系。
  3. 一个没有Parent id的span成为root span,可以看成调用链入口。
  4. 所有这些ID可用全局唯一的64位整数表示;
  5. 整个调用过程中每个请求都要透传TraceID和SpanID。
  6. 每个服务将该次请求附带的TraceID和附带的SpanID作为Parent id记录下,并且将自己生成的SpanID也记录下。
  7. 要查看某次完整的调用则只要根据TraceID查出所有调用记录,然后通过Parent id和Span id组织起整个调用父子关系。

调用链实现技术

采用字节码插桩方式,监听JVM虚拟机。好处是对代码侵入为零。

首先需要了解JVM的classLoader加载机制。

  1. JVM的classLoader主要分为两类,一种是初始加载器,用来加载lib下的所有后缀为.jar的文件。另一种是所有其他类型的加载器,包括自定义的class Loader.
  2. 加载机制采用双亲委派模式,就是所有的加载器需要加载的时候都会先交给父类去加载,只有当父类加载不了,才会使用自身的加载功能,如此一来,所有的加载都绕回了初始加载器。
  3. 例如object类定义在rt.jar中,所有的对象都是Object,都需要先加载Object类,进而都需要加载rt.jar文件,进而需要使用初始加载器来加载。

Javassist

  1.  Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而 且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成。
  2. 作用:AOP动态代理。获取访问类结构信息,如参数名称等。运行时监控插桩埋点。
  3. 使用流程:
  • 构建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中出现了该类,就不管了,不会继续向上请求加载。

猜你喜欢

转载自blog.csdn.net/Damon__Wang/article/details/81782911