Java的Agent字节码增强&链路追踪
2024-12-12 09:18:45 3 举报
AI智能生成
Java的Agent字节码增强&链路追踪是一种在Java应用程序运行时,通过拦截并修改其字节码,来实现功能增强和性能监控的技术。这种技术无需修改源代码,即可实现对Java应用程序的透明化改造。Agent字节码增强可以用于实现链路追踪、性能监控、安全审计等功能。链路追踪可以记录并展示一次请求在处理过程中的路径,帮助开发者快速定位问题。通过在应用程序的请求处理流程中注入链路追踪的代码,可以追踪请求的处理过程,收集性能数据,并在可视化工具中展示,为开发者提供实时的请求处理信息。
作者其他创作
大纲/内容
深入理解字节码增强,玩转javassist
Java体系结构<br>Java应用: 我们平时开发的程序就属于这个范畴<br>Java Agent:Java层面 代表作Arthas<br>JMX: jconsole、Java层面,jps<br>Serviceability Agent: 属于JVM层面,代表作品HSDB<br><br>提供这么多机制,主要做两件事<br>1.调试工具<br>2.监控工具<br><br>JNI早期是为了Java与C兼容,现在是提供JNI调用底层的能力
字节码增强分为两个部分来理解<br>1.字节码 (.class文件)<br>2.增强<br><br>字节码增强需要编译系统和运行系统工作协作。<br>编译系统通过静态编译javac输出成.class文件,然后作为运行系统的输入<br><br>一个Java应用需要调试和监控
目前主流的字节码增强技术有bcel、ASM、javassit、byte-buddy(Skywalking使用的技术)<br>为什么ASM字节码增强技术性能高?(从调用方法层面)<br>增强的代码技术分为两类<br>1.写Java代码 面向Java开发<br>2.字节码增强包 面向字节码开发<br><br>其中bcel、ASM技术可以直接生成字节码,而javassist、byte-budy是通过生成Java代码,然后再编译<br><br>性能评判标准是:越接近字节码,性能越高,变成难度越大<br><br>代码请见,如果觉得有用的话,还请点个star<br>https://github.com/2over/my-agent <br>
目前JDK中获取一个类的信息的方式<br>1.反射(这也是JDK动态代理使用的方式)<br>2.MethodProxy.create(CGLIB使用的方式)<br>3.RuntimeSupport.find2Method(javassist中使用的方式)<br>4.MethodHandle(Lambda表达式中使用的方式)
JDK动态代理
CGlib动态代理
javassist
Lambda表达式中使用的方式
自实现动态代理机制
jdk与cglib动态代理的区别<br>jdk:<br>1.实现接口<br>2.invoke的三个参数:代理对象的实例、被代理类中的方法、方法参数<br>3.生成的代理类中不会生成增强方法<br>4.在invoke中通过反射直接调用被代理类中的方法<br><br>cglib:<br>1.代理类与被代理类时父子关系<br>2.invoke的四个参数:代理对象的实例、被代理类中的方法、方法参数、生成的代理类中的方法<br>add->intercept->CGLIB$add$0->super.add<br><br>对比:<br>1.不传代理对象的实例,传Class对象行不行?<br>不行,如果构造函数有参数就行不通<br>2.cglib生成的增强方法是否多余?为什么?<br>有点多余,<br>3.handler中的invoke方法中的第一个参数是代理对象的实例,有什么用?<br>还不得而知<br><br>Cglib是新生成了一个类,增强了方法,生成的代理类中可以直接调用原方法<br>Jdk新生成了一个类,没有增强方法,生成的代理类中不可以直接调原方法
JDK的invoke
Cglib的invoke
如果想要增强一个类,有如下途径可以实现<br>1.增强原有类/需要满足几个条件,一是能够增强类(javassist),二是需要被jvm载入,但是需要借助agent的热更新<br>2.创建新的类,反而更简单一些
目前支持动态代理的机制有如下几种:<br>1.jdk<br>2.cglib<br>3.javassit<br>4.自实现
深入理解Agent
Agent用途<br>1.监控工具,随Java应用一同启动<br>2.调试工具,通过attach机制与Java应用建立通信
Agent用法<br>1.随java应用启动<br>```java<br>// 会优先运行这个premain<br>public static void premain(String agentArgs, Instrumentation inst);<br><br>// 如果没有两个参数的premain,就运行一个参数的<br>public static void premain(String agentArgs);<br>```<br>用法: 等号后面的是传参,多个参数需要自己在程序中解析<br>-javaagent:jaavagent-demo-1.0.jar=mode=test<br><br>2.attach<br>```java<br>// 会优先运行这个agentmain<br>public static void agentmain(String agentArgs, Instrumentation inst);<br><br>// 如果没有两个参数的agentmain,就运行一个参数的<br>```<br>agentArgs:就是外部传给agent程序的参数,如果是多个参数,建议采用这个格式,用下面这段代码解析<br>eg:javaagent-demo-1.0.jar=mode=test;name=cover;age=18<br><br>```java<br>private static Map<String, String> parseArgs(String args) {<br> Map<String, String> ret = new HashMap<>();<br> String[] argsArr = args.split(";");<br> for (String arg : argsArr) {<br> String[] strings = arg.split("=");<br> ret.put(strings[0], strings[1]);<br> }<br><br> return ret;<br>}<br>```
Instrumentation这个对象是什么?为什么premain和agentmain都需要传这个参数?<br>先来看一个重要的数据结构:_JPLISAgent,全称:Java Programming Language Instrumentation Services Agent<br>各属性的含义:<br>1.mNormalEnvironment:主要提供正常的类transform及redefine功能的<br>2.mRetransformEnvironment:主要提供类retransform功能的<br>3.mInstrumentationImpl:这个对象非常重要,也是我们java agent和JVM进行交互的入口,写过javaagent的人在写premain和agent方法的时候注意到了有个Instrumentation的参数,这个参数其实就是这里的对象<br>4.mPremainCaller:指向sun.instrument.InstrumentationImpl#loadClassAndCallPremain方法,如果agent是在启动的时候加载的,那该方法会被调用<br>5.mAgentmainCaller:指向sun.instrument.InstrumentationImpl#loadClassAndCallAgentmain,该方法在通过attach的方式动态加载agent的时候调用<br>6.mTransform: 指向sun.instrument.InstrumentationImpl#transform方法<br>7.mAgentClassName:在我们javaagent的MANIFEST.MF里指定的Agent-Class<br>8.mOptionString:传给agent的一些参数<br>9.mRedefineAvailable:是否开启了redefine功能,在javaagent的MANIFEST.MF里设置CanRedefine-Classes:true<br>10.mNativeMethodPrefixAvailable:是否支持native方法前缀设置,同样在javaagent的MANIFEST.MF里设置Can-Set-Native-Method-Prefix:true<br>11.mIsRetransformer:如果在javaagent的MANIFEST.MF文件里定义了Can-Retransform-Classes:true,那将会设置mRetransformEnvironment的mIsRetransformer为true
Inst对象是何时创建的?<br>Agent_OnAttach<br>
success = createInstrumentationImpl(jni_env, agent);
premain何时被调用
在Threads::create_vm中调用JvmtiExport::post_vm_initialized
Agent_OnLoad进来
createNewJPLISAgent创建JPLISAgent对象
initializeJPLISAgent中调用了eventHandlerVMInit
接着里面调用了processJavaStart
这个mPremainCallerMethod就是sun.instrument.InstrumentationImpl#loadClassAndCallPremain
loadClassAndCallPremain何时被调用
mainCallingMethod是前面processJavaStart方法传递过来的参数
agentmain何时被调用<br>HotSpot源码:Agent_OnAttach<br>Java代码:sun.instrument.InstrumentationImpl#loadClassAndCallAgentmain<br><br><br>
如何监控类的加载?<br>这其实涉及到JVMTI Agent了,目前暂时不去深入<br>https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#GetLoadedClasses<br>
attach的本质<br>首先从java端virtualMachine = VirtualMachine.attach(pid);开始<br>接着是LinuxAttachProvider<br><br>Agent进程是一个独立的进程,attach底层是通过socket底层实现,不是通过tcp、udp而是unix域 127.0.0.1回环网卡通信<br>
再者是LinuxVirtualMachine
attach底层是通过socket实现的
是基于本地域实现的socket,保证通信效率<br>也可以使用命令netstat -apnl | grep java根据进程id去查看
监控的时候就需要用到premain<br>热更新的时候需要用到agentmain,需要attach
实现链路追踪引擎及热更新
背景<br>自SpringCloud问世以来,微服务以席卷之势风靡全球,企业架构都在从传统的SOA向微服务转型。然而微服务这把双刃剑在带来各种优势的同时,也给运维、性能监控、错误的排查带来了极大的困难。<br>在大型项目中,服务架构会包含数十乃至上百个服务节点。往往一次请求会涉及到多个微服务,想要排查一次请求链路中经过了哪些节点,每个节点的执行情况如何,就成为了亟待解决的问题,于是分布式系统的APM管理系统应运而生。<br>
什么是APM系统?<br>APM系统可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这就是APM系统,<br>全称是Application Performance Monitor<br><br>谷歌公开的论文提到的Google Dapper可以说是最早的APM系统了,给google的开发者和运维团队帮了大忙,所以谷歌公开论文分享了Dapper.而后,很多的技术攻击基于这篇论文的原理,涉及开发了很多出色的APM框架,例如Pinpoint、SkyWalking等。而SrpingCloud官网也集成了一套这样的系统:SpringCloud Sleuth,结合Zipkin.<br>
APM的基本原理<br>目前大部分的APM系统都是基于Google的Dapper原理实现,例如一次请求调用示例:
如何才能实现追踪呢?<br>Google的Dapper涉及了下面的几个概念用来记录请求链路:<br>Span: 请求中的基本工作单元,每一次链路调用(RPC、Rest、数据库调用)都会创建一个Span。结构如下<br>type Span struct {<br> TraceID int 64 // 用于表示一次完整的请求id<br> Name string // 单元名称<br> ID int64 // 当前这次调用span_id<br> ParentID int64 // 上层服务的span_id,最上层服务parent_id为null,代表根服务<br> Annotation[] Annotation // 注释,用于记录被调用中的详细信息,例如时间<br>}<br>Trace: 一次完整的调用链路,包含多个Span的树状结构,具有唯一的TraceID<br>一次请求的每个链路,通过spanId、parentId就能串联起来;<br>当然,从请求到服务器开始,服务器返回Response结束,每个span存在相同的唯一标识trace_id
APM的筛选标准<br>目前主流的APM框架都会包含下列几个组件来完成链路信息的收集和展示:<br>1.探针(Agent):负责在客户端程序运行时搜索服务调用链路信息,发送给收集器<br>2.收集器(Collector):负责将数据格式化,保存到存储器<br>3.存储器(Storage):保存数据<br>4.UI界面(WebUI):统计并展示收集到的信息<br>因此,要筛选一款合格的APM框架,就是对比各个组件的使用差异,主要对比项:<br>1.探针的性能<br>主要是agent对服务的吞吐量、CPU和内存的影响。如果探针在收集微服务运行数据时,对微服务的运行产生了比较大的性能影响,相信没什么人愿意使用。<br>2.collector的可扩展性<br>能够水平扩展以便支持大规模服务器集群,保证收集器的高可用特性<br>3.全面的调用链路数据分析<br>数据的分析要快,分析的维度尽可能多。跟踪系统能够提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应,最好提供源码级别的可见性以便轻松定位失败点和瓶颈<br>4.对于开发透明,容易开关<br>即也作为业务组件,应当尽可能少入侵或者无入侵其他业务系统,对于使用方透明,减少开发人员的负担<br>5.完整的调用链应用拓扑<br>自动检测应用拓扑,帮助我们搞清楚应用的架构
目前主流的APM框架分别是:<br>1.Zipkin:由Twitter公司开源,开放源代码分布式的追踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展示<br>2.Pinpoint:一款对Java编写的大规模分布式系统的APM工具,由韩国人开源的分布式追踪组件<br>3.Skywalking:国产的的优秀APM组件,是一个对Java分布式应用程序集群的业务运行情况进行追踪、告警和分析的系统。现在时Apache的顶级项目之一。<br><br>可见,zipkin的探针性能、开发透明性、数据分析能力都不占优,实在是下下之选。<br>而pinpoint在数据分析能力、开发透明性上有较大的优势,不过Pinpoint的部署相对比较复杂,需要的硬件资源较高。<br>Skywalking的探针性能和开发透明性上具有较大优势,数据分析能力也还不错,重要的是其部署比较方便灵活,比起Pinpoint更适合中小型企业使用
为什么要有APM框架,不直接用专门的日志收集或者AOP、动态代理来做监控?<br>它们有一个共同的缺点就是需要改代码,这类技术是一种侵入式的,而APM是无侵入式的监控系统
Agent技术主要分为两种<br>1.监控 通过premain实现<br>2.调试 通过agentmain vm.attach<br><br>增强一个类一般来说,分为两种:<br>1.新创建一个类<br>2.增强原有类,增强方法
transform能够直接拦截所有类的原理<br>transform是运行在类加载阶段的加载和链接之间的一种技术手段<br>(加载->trasform 监控 -> 链接 -> 初始化 -> redefine 热更新 -> 卸载)<br>对应的HotSpot源码部分在.class->parseClassFile->JvmtiExport::post_load_class();<br>JavaAgent底层是基于JVMTI Agent生成的<br><br>如果是在初始化阶段之后做增强,这就属于热更新了redefine<br>
这个方法会判断有没有hook方法,如果有,则调用到transform
热更新的时候会STW,如果类加载初始化完成了,希望重新加载,可以使用redefine技术.<br>关键的方法:<br>1.VirtualMachine.attach(pid); 开启unix域类型的socket<br>2.VirtualMachine.loadAgent(jarPath, agentArgs); 把jar包加载进去
0 条评论
下一页
为你推荐
查看更多