您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 企业文化 > Java 运行时监控第 2 部分 编译后插装和性能监控
简介正如您在本系列(共三篇文章)的第1部分中所了解到的,监控Java应用程序的可用性和性能及其生产中的依赖性,这对于确保问题检测和加速问题诊断和修复至关重要。需要进行监视的类的源代码级插装具有第1部分所论述过的那些优势,但是这种方法通常都不可取或者不切实际。例如,很多您所感兴趣的监控点可能位于第三方组件中,而第三方组件的源代码您是不得而知的。在第2部分中,我着重介绍了无需修改原始源代码而插装Java类和资源的方法。可选择的在源代码外编排插装的方法有:截取类包装字节码插装本文使用了第1部分中呈现的ITracer接口来实现性能数据跟踪,依次举例阐明了这些技巧。通过截取进行Java插装截取的基本前提是通过一个截取构造和收集传入的入站与出站调用信息,对特定的调用模式进行转换。一个基本的截取程序的实现会:1.获取对入站调用请求的当前时间。2.取回出站响应的当前时间。3.将运行时间作为两次度量的增量计算出来。4.将调用的运行时间提交给应用程序性能管理(APM)系统。图1展示了该流程:图1.性能数据收集截取程序的基本流程很多诸如JavaPlatform和EnterpriseEdition(JavaEE)这样的Java框架都包括对截取栈的核心支持,服务的调用可以在截取栈中通过一系列预处理和后处理组件来进行传递。有了这些栈就可以很好地将插装注入到执行路径中,这样做的好处有二:第一,无需修改目标类的源代码;第二,只要将截取程序类插入到JVM的类路径中并修改组件的部署描述符,这样就把插装截取程序插入到了执行流程中。截取的核心指标截取程序所收集的一个典型的指标就是运行时间。其他的指标同样适合截取模式。我将介绍支持这些指标的ITracer接口的两个新的方面,所以在这里我要转下话题,先简要论述一下这些指标。使用截取程序时需要收集的典型指标有:运行时间:完成一个执行的平均时钟时间。每个时间间隔内的调用:调用目标的次数。每个时间间隔内的响应:目标响应调用的次数。每个时间间隔内的异常l:目标调用导致异常的次数。并发性:并发执行目标的线程数。清晰的界限变更管理的爱好者可能会对通过源代码实现变更和通过配置实现变更之间的差异持有争议。诚然,“代码”、XML和“脚本”之间的界限变得有些模糊了。但是下面两个变更之间还存在明显的界限:需要改变源代码的变更,接着还要编译、打包,有时还会涉及到一系列看起来无休止的预部署过程对(未改变的)二进制代码外部的资源所作的变更这两种变更之间最主要的差异是实现前滚(roll-forward)和后滚(roll-back)的简单性。在某些情况下,这种差异可能在理论上说不通,或者可能低估了某些环境的复杂度或变更过程的严格性。还有两个ThreadMXBean指标可以选择,但它们的作用有限,而且收集成本会高一些:运行CPU时间:这是线程在执行期间消耗的CPU时间,以纳秒为单位。CPU的利用情况在起初时似乎有用,但其实也就是作为一种趋势模式,其他的用处不大。或者,如果收集成本特别高的话,可以计算线程在执行时占用CPU资源的百分比的近似值。阻塞/等待计数和时间:等待表示由具体线程调度导致的同步或者等待。阻塞常见于执行等待资源时,如响应来自远程数据库的Java数据库连接(JavaDatabaseConnectivity,JDBC)调用(至于这些指标的用处,请参见本文的JDBC插装部分)。为了澄清ThreadMXBean指标的收集方法,清单1快速回顾了基于源代码的插装。在这个例子中,我针对heavilyInstrumentedMethod方法实现了大量插装。清单1.实现大量插装的方法1.protectedstaticAtomicIntegerconcurrency=newAtomicInteger();2..3..4.for(intx=0;xloops;x++){5.tracer.startThreadInfoCapture(CPU+BLOCK+WAIT);6.intc=concurrency.incrementAndGet();7.tracer.trace(c,SourceInstrumentation,heavilyInstrumentedMethod,8.ConcurrentInvocations);9.try{10.//===================================11.//Hereisthemethod12.//===================================13.heavilyInstrumentedMethod(factor);14.//===================================15.tracer.traceIncident(SourceInstrumentation,16.heavilyInstrumentedMethod,Responses);17.}catch(Exceptione){18.tracer.traceIncident(SourceInstrumentation,19.heavilyInstrumentedMethod,Exceptions);20.}finally{21.tracer.endThreadInfoCapture(SourceInstrumentation,22.heavilyInstrumentedMethod);23.c=concurrency.decrementAndGet();24.tracer.trace(c,SourceInstrumentation,25.heavilyInstrumentedMethod,ConcurrentInvocations);26.tracer.traceIncident(SourceInstrumentation,27.heavilyInstrumentedMethod,Invocations);28.}29.try{Thread.sleep(200);}catch(InterruptedExceptione){}30.}31.清单1引入了两个新的构造:ThreadInfoCapture方法:ThreadInfoCapture方法对于获取运行时间和目标调用前后的ThreadMXBean指标增量都很有帮助。startThreadInfoCapture为当前线程捕获基准,而endThreadInfoCapture计算出增量和趋势。由于这些指标永远都是递增的,所以必须事先确定一个基准,再根据它计算出之后的差量。但这个场景不适用于跟踪程序的增量功能,这是因为每一个线程的绝对值都是不同的,而且运行中的JVM中的线程也不是保持不变的。另外还要注意跟踪程序使用了一个栈来保存基准,所以您可以(小心地)嵌套调用。要收集这个数据可是要费一番力气。图2展示了收集各种ThreadMXBean指标所需要的相对运行时间:图2.收集ThreadMXBean指标所需的相对成本虽然如果小心使用调用的话,收集这些指标的总开销不会很大,但是仍然需要遵循在记录日志时需要考虑的一些事项,例如不要在紧凑循环(tightloop)内进行。并发性:要跟踪在特定时间内通过这个代码的线程数,需要创建一个计数器,该计数器既要是线程安全的又要对目标类的所有实例可用—在本例为AtomicInteger类。此种情况比较麻烦,因为有时可能多个类加载器都载入了该类,致使计数器无法精确计数,从而导致度量错误。解决这个问题的办法为:将并发计数器保存在JVM的一个特定的受保护位置中,诸如平台代理中的MBean。并发性只有在插装目标是多线程的或者共用的情况下可用,但是它是非常重要的指标,这一点我将在稍后介绍EnterpriseJavaBean(EJB)截取程序时进一步阐述。EJB截取程序是我接下来要论述的几个基于截取的插装示例的第一个,借鉴了清单1中查看的跟踪方法。EJB3截取程序发布了EJB3后,截取程序就成了JavaEE架构中的标准功能(有些Java应用服务器支持了EJB截取程序一段时间)。大多数JavaEE应用服务器的确提供了性能指标,报告有关诸如EJB这样的主要组件,但是仍然需要实现自己的性能指标,因为:您需要基于上下文的或者基于范围/阈值的跟踪。应用服务器指标固然不错,但是您希望指标位于APM系统中,而不是应用服务器中。应用服务器指标无法满足您的要求。虽然如此,根据您的APM系统和应用服务器实现的不同,有些工作可能不用您再亲历亲为了。例如,WebSphere®PMI通过Java管理扩展(JavaManagementExtensions,JMX)公开了服务器指标(参见参考资料)。即使您的APM供应商没有提供自动读取这个数据的功能,读完本篇文章之后您也会知道如何自行读取。在下一个例子中,我将向一个称为org.aa4h.ejb.HibernateService的无状态会话的上下文bean中注入一个截取程序。EJB3截取程序的要求和依赖性都是相当小的:接口:javax.interceptor.InvocationContext注释:javax.interceptor.AroundInvoke目标方法:任何一个名称里面有publicObjectanyName(InvocationContextic)的方法清单2展示了样例EJB的截取方法:清单2.EJB3截取程序方法1.@AroundInvoke2.publicObjecttrace(InvocationContextctx)throwsException{3.ObjectreturnValue=null;4.intconcur=concurrency.incrementAndGet();5.tracer.trace(concur,EJBInterceptors,ctx.getTarget().getClass()6..getName(),ctx.getMethod().getName(),7.ConcurrentInvocations);8.try{9.tracer.startThreadInfoCapture(CPU+BLOCK+WAIT);10.//===================================11.//Thisisthetarget.12.//===================================13.returnValue=ctx.proceed();14.//===================================15.tracer.traceIncident(EJBInterceptors,ctx.getTarget().getClass()16..getName(),ctx.getMethod().getName(),Responses);17.concur=concurrency.decrementAndGet();18.tracer.trace(concur,EJBInterceptors,ctx.getTarget().getClass()19..getName(),ctx.getMethod().getName(),20.ConcurrentInvocations);21.returnreturnValue;22.}catch(Exceptione){23.tracer.traceIncident(EJBInterceptors,ctx.getTarget().getClass()24..getName(),ctx.getMethod().getName(),Exceptions);25.throwe;26.}finally{27.tracer.endThreadInfoCapture(EJBInterceptors,ctx.getTarget()28..getClass().getName(),ctx.getMethod().getName());29.tracer.traceIn
本文标题:Java 运行时监控第 2 部分 编译后插装和性能监控
链接地址:https://www.777doc.com/doc-4868146 .html