SpringCloud(六) 微服务架构-APM监控
APM (ApplicationPerformance Management) 即应用性能管理,致力于监控和管理应用软件性能和可用性,通过监测和诊断复杂应用程序的性能问题,来保证应用程序的良好运行。主要特征有三个特征:
- 多级应用性能监控:覆盖通讯协议1-7层,通过事务处理过程监控、模拟等手段实现端到端应用监测。
- 应用性能故障快速定位:对应用系统各个组件进行监测,迅速定位系统故障,并进行修复或提出修复建议。
- 应用性能全面优化:精确分析各组件占用系统资源的情况,并根据应用系统性能要求给出专家建议
常见的APM工具如下:
Zipkin+Sleuth | Pinpoint | SkyWalking | CAT | |
开发者 | 韩国 | Apache | 美团 | |
实现方式 | 拦截请求,发送(HTTP,mq)数据至zipkin服务 | java探针,字节码增强 | java探针,字节码增强,配置文件 | 代码埋点(拦截器,注解,过滤器等) |
接入方式 | 基于linkerd或者sleuth方式,引入配置即可 | javaagent字节码 | javaagent字节码 | 代码侵入 |
agent到collector的协议 | http,MQ | thrift,DUBBO,gRPC | gRPC | http/tcp |
OpenTracing | 支持 | x | 支持 | x |
颗粒度 | 接口级 | 方法 | 方法 | 代码级 |
全局调用统计 | x | 支持 | 支持 | 支持 |
traceid查询 | 支持 | x | 支持 | x |
虚拟机监控 | x | x | JVM、CLR | 支持 |
健壮度UI面板 | 中 | 优 | 良 | 优 |
数据存储 | es,mysql,Cassandra,mem | Hbase | ES,H2,mysql,influxdb | mysql,hdfs |
JavaAgent
JDK 1.5以后引入了JavaAgent技术,JavaAgent是运行方法之前的拦截器。利用JavaAgent和ASM字节码技术,可以在JVM加载class二进制文件时动态修改加载的class文件。通过在启动脚本中添加-javaagent://agent.jar,需重启生效。主流的开源框架有Pinpoint、SkyWalking等。 虚拟机的类加载机制
运行java命令,可用得到如下提示: -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<选项>] 按完整路径名加载本机代理库 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument
该包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型。其中,使用该软件包的一个关键组件就是 Javaagent。从名字上看,似乎是个 Java 代理之类的,而实际上,他的功能更像是一个Class 类型的转换器,他可以在运行时接受重新外部请求,对Class类型进行修改。从本质上讲,Java Agent 是一个遵循一组严格约定的常规 Java 类,并且对该 java 包有2个要求:
- 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
- Premain-Class 指定的那个类必须实现 premain() 方法。
premain 方法,从字面上理解,就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行-javaagent
所指定 jar 包内 Premain-Class 这个类的 premain 方法 。
instrument
instrument
的底层实现依赖于JVMTI(JVM Tool Interface)
,它是JVM暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。JVMTIAgent
是一个利用JVMTI
暴露出来的接口提供了代理启动时加载(agent on load)、代理通过attach形式加载(agent on attach)和代理卸载(agent on unload)功能的动态库。而instrument agent
可以理解为一类JVMTIAgent
动态库,别名是JPLISAgent(Java Programming Language Instrumentation Services Agent)
,也就是专门为java语言编写的插桩服务提供支持的代理。
①启动时加载instrument agent过程:
-
创建并初始化 JPLISAgent;
-
监听
VMInit
事件,在 JVM 初始化完成之后做下面的事情:-
创建 InstrumentationImpl 对象 ;
-
监听 ClassFileLoadHook 事件 ;
-
调用 InstrumentationImpl 的
loadClassAndCallPremain
方法,在这个方法里会去调用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 类的 premain 方法 ;
-
-
解析 javaagent 中 MANIFEST.MF 文件的参数,并根据这些参数来设置 JPLISAgent 里的一些内容。
②运行时加载instrument agent过程:
通过 JVM 的attach机制来请求目标 JVM 加载对应的agent,过程大致如下:
- 创建并初始化JPLISAgent;
- 解析 javaagent 里 MANIFEST.MF 里的参数;
- 创建 InstrumentationImpl 对象;
- 监听 ClassFileLoadHook 事件;
- 调用 InstrumentationImpl 的
loadClassAndCallAgentmain
方法,在这个方法里会去调用javaagent里 MANIFEST.MF 里指定的Agent-Class
类的agentmain
方法。
③ Instrumentation的局限性:
大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,或者笼统说就是类重定义(Class Redefine)的功能,但是有以下的局限性:
- premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
- 类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses()方法,此方法有以下限制:
- 新类和老类的父类必须相同;
- 新类和老类实现的接口数也要相同,并且是相同的接口;
- 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致;
- 新类和老类新增或删除的方法必须是private static/final修饰的;
- 可以修改方法体。
除了上面的方式,如果想要重新定义一个类,可以考虑基于类加载器隔离的方式:创建一个新的自定义类加载器去通过新的字节码去定义一个全新的类,不过也存在只能通过反射调用该全新类的局限性。
参考:https://www.cnblogs.com/aspirant/p/8796974.html
https://www.cnblogs.com/rickiyang/p/11368932.html#724999729
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------