Java 系统运行时性能和可用性监控
简介:
当今的许多 Java 应用程序都依赖于一组复杂的分布式依赖关系和移动部件。很多外部因素都可能对应用程序的性能和可用性造成影响。这些影响基本上都无法完全消除或解决,且难以在预生成环境中准确模拟。Stuff happens。但是,您可以创建并维护一个全面的系统来监控应用程序的整个生态系统,从而显著降低这些事件的严重性和持续时间。
本系列文章给出了实现此类系统的一些模式和技巧。模式,以及我将使用的一些术语,都表示泛指。通过结合示例代码和插图,它们将帮助您理解应用程序性能监控的概念。这种理解强调解决方案的必要性,并能帮助您选择商业或开源的解决方案。您可以扩展和定制一个解决方案,或者根据需要将其作为设计解决方案的蓝图。
第 1 部分:
第 2 部分将重点介绍插装 Java 类及资源而无需修改原始源代码的方法。第 3 部分将论述监控 JVM 外部资源的方法,包括主机及其操作系统以及数据库和消息传递系统等远程服务。它还将总结并归纳其他的 APM 问题,如数据管理、数据虚拟化、报告和报警。
APM系统:模式和反面模式
为让大家正确入门,应当强调,虽然此处介绍的多数与 Java 相关的内容看上去与应用程序和代码性能分析的流程类似,但其实并非如此。性能分析是一个极具价值的生产前流程,它可以确认您的 Java 代码是否可扩展、高效、快速和足够出色。但是,根据stuff happens公理,当您在生产中遇到无法说明的问题时,优秀的开发阶段代码性能分析可能无用武之地。?
监控反面模式?
完全没有监控资源的应用程序微乎其微,但仍然需要考虑这些反面模式,它们经常出现在运行环境中:?
图 1 对比了孤立和整合的 APM 系统:
图 1. 孤立和整合 APM 系统的对比
?
整合 APM 的实现并不排除监控和诊断工具,如 DBA 管理工具集、低级网络分析应用程序和数据中心管理解决方案。这些工具仍然是无价的资源,但如果它们依赖于整合视图的专有性,则难以克服孤立效果的影响。
APM?系统概念
跟踪类别和名称?
ITracer 的基本前提是向中央 APM 系统提交一个度量和相关的名称。此活动由 trace 方法实现,该方法因提交的度量而有所不同。各 trace 方法都接受一个 String[] name 参数,其中包含复合名称的上下文组件,其结构特定于 APM 系统。复合名称向 APM 系统指示提交的名称空间和实际的指标名称;因此,复合名称中通常至少包括根类别和度量说明。底层 ITracer 实现应该知道如何通过传递的 String[] 构建复合名称。表 1 演示了复合命名约定的两个示例:
表 1. 示例复合名称
?
?
名称结构
复合名称
简单斜杠分隔
Hosts/SalesDatabaseServer/CPU Utilization/CPU3
JMX MBean ObjectName
com.myco.datacenter.apm:type=Hosts,service=SalesDatabaseServer,group=CPU Utilization,instance=CPU3
?
清单 1 是使用此 API 跟踪调用的简短示例:
清单 1. 跟踪 API 调用示例
?
ITracer simpleTracer = TracerFactory.getInstance(sprops);?
跟踪程序度量数据类型?
在此接口中,度量数据可以是以下类型:
APM 系统提供商可能支持其他数据类型的收集度量数据。?
跟踪程序类型?
选定了具体的度量数据类型(如 long)之后,可以根据 APM 系统支持的类型来选择解释特定值的方式。还需记住,各 APM 实现可以使用不同的术语来表示本质相同的类型,并且 ITracer 使用了一些通用的命名规则.
ITracer 中表示的跟踪程序类型:
TracerFactory 是一个普通的工厂类,用于根据传递的配置属性创建新 ITracer 实例,或者从缓存中引用已创建的 ITracer。
收集器模式?
收集通常有三种可选模式,这影响到应该使用的跟踪程序类型:?
轮询:按固定频繁调用收集器,它将检索和跟踪 PDS 中的指标或指标集的当前值。例如,可以每分钟调用一次收集器来读取主机的 CPU 利用率,或通过 JXM 接口从事务管理器读取提交事务的总数。轮询模式的前提是对目标指标的定期采样。因此,对于轮询事件,指标的值将提供给 APM 系统,但是,假定中间时期的值不变。因而,轮询收集器通常使用粘附跟踪程序类型:APM 系统在生成报告时将假定所有轮询事件之间的值不变。
图 4 演示了此模式:
图?4.?轮询收集模式??

图 5:监听收集模式
?
监听:这种通用数据模式是 Observer 模式的一种形式。收集器将其自身注册为目标 PDS 的事件监听程序,它将在相关的事件发生时接受回调。作为回调结果发出的跟踪值取决于回调有效负荷本身的内容,但收集器至少可以跟踪每个回调的事件。图 5 演示了此模式:?
?
图 5:监听收集模式?

由于您可以假定截取收集器能 “看到” 每一个事件,因此实现的跟踪程序通常为平均时间间隔类型。因此,如果时间间隔到期且没有活动发生,则该时间间隔的聚合值将为零,而与之前时间间隔中的活动无关。
图 6 演示了此模式:
现在,我已经介绍了性能数据跟踪 API、它的底层数据类型和数据收集的模式。接下来,我将通过一些用例和示例来演示 API 的应用。
监控 JVM
从 JVM 开始实现性能监控是个明智的选择。首先,我将介绍所有 JVM 共同的性能指标,然后再介绍企业给应用程序中经常使用的一些 JVM 驻留组件。通常,Java 应用程序实例是受底层操作系统支持的进程,因此,JVM 监控的某些方面最好是从主机 OS 的视角来理解,这些内容将在第 3 部分中介绍。?
在 Java Platform, Standard Edition 5 (Java SE) 发行之前,能够在运行时有效和可靠收集的内部及标准化 JVM 诊断信息非常有限。现在,java.lang.management 接口提供了一些有用的监控点,该接口是所有兼容 Java SE 5(和更新版本)的 JVM 版本的标准。这些 JVM 的某些实现提供了额外的属性指标,但是它们的访问模式却基本相同。我将重点介绍可以通过 JVM 的 MXBeans 访问的标准模式 — 部署在 VM 内部的 JMX MBeans 公开了一个管理和监控接口?
?
在本地部署中,收集器和它的调用调度程序部署在目标 JVM 中。随后,JMX 收集器组件将使用 PlatformMBeanServer(可以通过 JVM 内部的 MBeanServerConnection 来连接它)访问 MXBeans。在远程部署中,收集器运行在一个单独的进程中,并使用某种形式的 JMX Remoting 来连接目标 JVM。这可能没有本地部署那么高效,但它不需要在目标系统中部署任何额外的组件。JMX Remoting 不在本文的讨论范围之内,但它的实现方法非常简单:部署一个 RMIConnectorServer 或在 JVM 中启用外部连接。
示例 JMX 收集器?
本文的示例 JMX 收集器包含三个单独的方法,可用于获取 MBeanServerConnection。该收集器可以:?
清单 2 是摘录自 JMXCollector collect() 方法的代码段,它显示了 ThreadMXBean 中的收集和线程跟踪活动。?
清单 2. 示例 JMX 收集器的 collect() 方法的部分代码,它使用 ThreadMXBean
?on = objectNameCache.get(THREAD_MXBEAN_NAME);
??????tracer.traceDeltaSticky((Long)jmxServer.getAttribute(on,"TotalStartedThreadCount"),
????????hostName, "JMX", on.getKeyProperty("type"), "StartedThreadRate");
??????tracer.traceSticky((Integer)jmxServer.getAttribute(on, "ThreadCount"), hostName,
????????"JMX", on.getKeyProperty("type"), "CurrentThreadCount");
.
.
??????// Done
??????long elapsed = System.currentTimeMillis()-start;
??????tracer.trace(elapsed, hostName, "JMX", "JMX Collector",
?????????"Collection", "Last Elapsed Time");
??????tracer.trace(new Date(), hostName, "JMX", "JMX Collector",
?????????"Collection", "Last Collection");????????
??????log("Completed JMX Collection in ", elapsed, " ms.");????????
???} catch (Exception e) {
??????log("Failed:" + e);
??????tracer.traceIncident(hostName, "JMX", "JMX Collector",
?????????"Collection", "Collection Errors");
???}
}
清单 2 中的代码将跟踪 TotalThreadsStarted 和 CurrentThreadCount 的值。由于它是轮询收集器,因此两个跟踪都使用粘附选项。但是,由于 TotalThreadsStarted 是一个不断增加的数值,因此最吸引人的地方不是绝对值,而是已创建线程的速率。这样,该跟踪程序将使用 DeltaSticky 选项。
图 7 显示了此收集器创建的 APM 指标树:??
JMX 收集器的一些方面并未显示在清单 2 中,比如说调度注册,它将每隔 10 分钟为 collect() 方法创建一个定期回调。
在清单 2 中,不同跟踪程序类型和数据类型的实现方法将由数据源决定。例如:?
为了追求效率,由于目标 MXBeans 的 JMX ObjectName 在目标 JVM 的生存期不会更改,因此收集器使用 ManagementFactory 常量名来缓存名称。
对于 MXBeans 的两种类型 — GarbageCollector 和 MemoryPool — 准确的 ObjectName 无法预先知晓,但是您可以提供一个通用的模式。在这些情况下,在初次执行收集时,您将对 MBeanServerConnection 发起一个查询,并请求与提供模式相匹配的所有 MBeans 的列表。为避免未来在目标 JVM 的生存期执行查询,返回的匹配 MBean ObjectName 将缓存在内存中。?
在某些情况下,收集的目标 MBean 属性可能不是纯数值类型。MemoryMXBean 和 MemoryPoolMXBean 就是这种情况。对于这些情况,属性类型是可查询键和值的 CompositeData 对象。对于 java.lang.management JVM 管理接口,MXBean 标准采用了 JMX?Open Types?模型,在该模型中,所有属性都是语言无关的类型,如 java.lang.Boolean 和 java.lang.Integer。或者,对于 javax.management.openmbean.CompositeType 等复杂类型,这些类型可以被分解为相同简单类型的键/值对。简单类型的完整列表枚举在静态 javax.management.openmbean.OpenType.ALLOWED_CLASSNAMES 字段中。该模型支持一个类型独立层,使 JMX 客户机不用依赖于非标准的类,并且还可以支持非 Java 客户机,因为底层类型相对比较简单。有关 JMX Open Types 的更多信息,对于目标 MBean 属性是非标准复杂类型的情况,您需要确保定义该类型的类在收集器的类路径中。并且,您必须实现一些自定义代码来呈现检索到的复杂对象中的有用数据。?
如果获取了单个连接并为所有收集保留了该连接,则需要通过错误检测和修复来创建一个新连接,以防止该连接出现故障。某些收集 API 提供断开监控程序,可以提示收集器关闭、消除和创建新连接。如果收集器尝试连接到由于维护而停机或由于其他原因而无法访问的 PDS,则收集器应该以合适的频率轮询并重新连接。跟踪连接的运行时间还可用于在检测到关机时减少收集的频率。这可以减少已超负荷运行了一段时间的目标 JVM 的开销。?
这些示例中未实现的两个额外技巧可以改进 JMX 收集器的效率,并减少它在目标 JVM 中运行所需的开销。第一个技巧适用于从一个 MBean 中查询多个属性的情况。借助 getAttributes(ObjectName name, String[] attributes),您可以在一个调用中请求多个属性,而不必使用 getAttribute(ObjectName name, String attribute) 一次请求一个属性。这种差异在本地收集中可以忽略,但是在远程收集中却可以显著减少资源的使用,因为它可以减少网络调用的数量。第二个技巧是使用监控收集模式代替轮询模式,从而进一步减少 JMX 公开内存池的轮询开销。MemoryPoolMXBean 支持建立一个使用率阀值,超过该阀值时将触发向监控程序发送一个通知,而监控程序将跟踪该值。当内存使用率增加时,使用率阀值可以相应地增加。这种方法是缺陷是,如果使用率阀值没有微小的增量,则一些粒度级的数据可能会丢失,并且阀值下方的内存使用率模式将变为不可见。?
最后一个未实现的技巧是测定运行时间和垃圾收集总运行时间的范围,并实现一些简单的算法来计算垃圾收集器处于活动状态的时间在已运行时间中的百分比。这是一个有用的指标,因为一些垃圾收集器(当前)是大多数应用程序必须要面对的问题。由于某些收集(分别执行了一段时间)是期望执行的,因此运行垃圾收集占用的时间可以更加清楚地反映 JVM 的内存健康状况。根据经验(因应用程序而大不相同),占用任何 15 分钟时间段内的 10% 以上则表示存在潜在问题。?
收集器的外部配置
为便于演示收集流程,本文介绍的 JMX 收集器经过了适当简化,但它仅限于硬编码的收集方式。理想情况下,收集器将实现数据访问方式,而外部提供的配置将提供内容。这种设计使收集器更具实用性,且易于重用。对于最高级别的重用,外部配置的收集器应该支持这些配置点:?
清单 3 演示了 JMX 收集器的外部配置:
?
清单?3. JMX?收集器的外部配置示例
<?xml version="1.0" encoding="UTF-8"?> <JMXCollector> ???<attribute name="ConnectionFactoryClassName"> ??????collectors.jmx.RemoteRMIMBeanServerConnectionFactory ???</attribute> ???<attribute name="ConnectionFactoryProperties"> ??????jmx.rmi.url=service:jmx:rmi://127.0.0.1/jndi/rmi://127.0.0.1:1090/jmxconnector ???</attribute> ???<attribute name="NamePrefix">AppServer3.myco.org,JMX</attribute> ???<attribute name="PollFrequency">10000</attribute> ???<attribute name="TargetAttributes"> ??????<TargetAttributes> ?????????<TargetAttribute objectName="java.lang:type=Threading" ????????????attributeName="ThreadCount" Category="Threading" ????????????metricName="ThreadCount" type="SINT"/> ????????????attributeName="TotalCompilationTime" Category="Compilation" ????????????metricName="TotalCompilationTime" type="SDINT"/> ??????</TargetAttributes>????? ???</attribute> </JMXCollector>
<TargetAttribute objectName="java.lang:type=Compilation"
通过?JMX?监控应用程序资源
目前为止,我已经讨论了通过 JMX 监控惟一标准的 JVM 资源。但是,许多应用程序架构,如 Java EE,可以通过 JMX 公开重要的特定于应用程序的指标(这取决于供应商)。一个典型的例子是 DataSource 利用率。DataSource 是一个用于将连接池化到外部资源(通常为数据库)的服务,这限制了并发连接的数量,以保护资源不受恶意应用程序的占用。监控数据源是整个监控计划中的关键环节。由于 JMX 抽象层,该流程与之前介绍的类似。?
下面是来自 JBoss 4.2 应用服务器实例的典型数据源指标:?
现在,收集器将使用批属性检索,并在一个调用中获取所有属性。惟一需要注意的是,您需要查询返回的数据,以接通不同的数据和跟踪程序类型。DataSource 指标在没有活动时也是不会变化的,因此,要使数值变化,您需要生成一些负载。清单 4 显示 DataSource 收集器的 collect() 方法:
?
清单?4. DataSource?收集器
public void collect() {
???try {
??????log("Starting DataSource Collection");
??????long start = System.currentTimeMillis();
??????ObjectName on = objectNameCache.get("DS_OBJ_NAME");
??????AttributeList attributes??= jmxServer.getAttributes(on, new String[]{
"AvailableConnectionCount",
????????????"MaxConnectionsInUseCount",
????????????"InUseConnectionCount",
????????????"ConnectionCount",
????????????"ConnectionCreatedCount",
????????????"ConnectionDestroyedCount"
??????});
??????for(Attribute attribute: (List<Attribute>)attributes) {
?????????if(attribute.getName().equals("ConnectionCreatedCount")
????????????|| attribute.getName().equals("ConnectionDestroyedCount")) {
???????????????tracer.traceDeltaSticky((Integer)attribute.getValue(), hostName,
???????????????"DataSource", on.getKeyProperty("name"), attribute.getName());
?????????} else {
????????????if(attribute.getValue() instanceof Long) {
???????????????tracer.traceSticky((Long)attribute.getValue(), hostName, "DataSource",
??????????????????on.getKeyProperty("name"), attribute.getName());
????????????} else {
???????????????tracer.traceSticky((Integer)attribute.getValue(), hostName,
??????????????????"DataSource",on.getKeyProperty("name"), attribute.getName());
????????????}
?????????}
??????}
??????// Done
??????long elapsed = System.currentTimeMillis()-start;
??????tracer.trace(elapsed, hostName, "DataSource", "DataSource Collector",
?????????"Collection", "Last Elapsed Time");
??????tracer.trace(new Date(), hostName, "DataSource", "DataSource Collector",
?????????"Collection", "Last Collection");????????
??????log("Completed DataSource Collection in ", elapsed, " ms.");????????
???} catch (Exception e) {
??????log("Failed:" + e);
??????tracer.traceIncident(hostName, "DataSource", "DataSource Collector",
?????????"Collection", "Collection Errors");
???}?????
}
?
图 8 显示了 DataSource 收集器的相应指标树:

监控?JVM?中的组件
本节介绍的技巧可用于监控应用程序组件、服务、类和方法。相关的主要区域如下:
使用 Java SE 5(和更新版本)ThreadMXBean 的一些实现提供的指标,还可以收集以下指标:
还可以使用备选工具集和本机接口来确定这些指标和其他指标,但这通常涉及某种级别的开销,从而造成不必要的生产运行时监控。已经说过,指标本身,甚至在收集时,是低级的。它们的作用也许仅限于分析趋势,并且很难与无法通过其他手段确定的因果效应相关联。
所有上述指标都可以通过插装类和方法的流程来收集,以便于收集和跟踪目标 APM 系统的性能数据。可以采用各种技巧来直接插装 Java 类,或者通过它们来间接计算性能指标:?
在本文的第 1 部分中,我只讨论基于源代码的插装;您将在第 2 部分中了解更多关于截取、字节码插装和类包装的信息。(从拓扑学的角度来说,截取、字节码插装和类包装的本质完全相同,但它们实现结果的操作有稍微不同的含义)。
异步插装
异步插装是类插装中的基本问题。上一节讨论了轮询性能数据的概念。如果轮询完成得足够好,则它应该不会对核心应用程序性能或开销造成影响。相反,插装应用程序代码本身会直接修改和影响核心代码的执行。任何插装的目标都必须是无论如何,不产生危害。开销损失必须尽可能接近忽略不计。事实上,消除测量本身中的极细微的损失是不可能的,但是,在获取性能数据之后,保持其余跟踪进程异步是非常重要的。可以采用若干种模式来实现异步跟踪。图 9 演示了异步跟踪的实现方法概览:?
?
图 9. 异步跟踪
?
源代码中的?Java?类插装
本节将讨论如何实现源代码级插装,并将提供一些最佳实践和示例代码。文章还介绍了一些新的跟踪结构,我将在源代码插装的上下文中阐明它们的操作和它们的插装模式。?
虽然其他选择已经流行,但源代码插装在某些实例中是无法避免的;在某些情况下,它是惟一的解决方案。借助一些明智的预防措施,它可以实现良好的效果。需要考虑的事项包括:?
上下文跟踪
上下文跟踪受具体的应用程序影响极大,但是可以考虑一个经过简化的例子:含有 processPayroll(long clientId) 方法的 payroll-processing 类。当被调用时,该方法计算并存储各客户员工的薪水。您可以通过各种方法插装该方法,但是,执行中的底层模式清楚表明,调用时间的增加与员工的数量不成比例。因此,研究 processPayroll 的运行时间趋势没有上下文可供参考,除非您知道程序每次处理的员工数量。简单来讲,对于特定的时间段,processPayroll 平均耗时?x?毫秒。无法确定这个值反映的性能是好还是坏,因为您不知道它处理的员工数量是 1 还是 150,而两种情况反映的性能差别巨大。清单 5 在代码中显示了这个简化的概念:?
清单?5.?上下文跟踪的例子
public void processPayroll(long clientId) {
???Collection<Employee> employees = null;
???// Acquire the collection of employees
???//...
???//...
???// Process each employee
???for(Employee emp: employees) {
??????processEmployee(emp.getEmployeeId(), clientId);
???}
}??
?
此处的主要挑战是,根据大多数插装技巧,processPayroll() 方法中的任何东西都是不可触及的。因此,虽然能够插装 processPayroll 甚至 processEmployee,但是却无法跟踪员工的数量,从而不能为方法的性能数据提供上下文。清单 6 显示了一个拙劣的硬编码示例(且有点效率不高),它将捕获上面提到的上下文数据:
public void processPayrollContextual(long clientId) {?????
???Collection<Employee> employees = null;
???// Acquire the collection of employees
???employees = popEmployees();
???// Process each employee
???int empCount = 0;
???String rangeName = null;
???long start = System.currentTimeMillis();
???for(Employee emp: employees) {
??????processEmployee(emp.getEmployeeId(), clientId);
??????empCount++;
???}
???rangeName = tracer.lookupRange("Payroll Processing", empCount);
???long elapsed = System.currentTimeMillis()-start;
tracer.trace(elapsed, "Payroll Processing", rangeName, "Elapsed Time (ms)");
???tracer.traceIncident("Payroll Processing", rangeName, "Payrolls Processed");
???log("Processed Client with " + empCount + " employees.");
?
清单 6 中的关键部分是 tracer.lookupRange 调用。Ranges 是指定的收集,它由数值范围限制键控,并且拥有一个表示数值范围名称的 String 值。不再跟踪薪水处理的简单无格式运行时间,清单 6 将员工计数划分为范围,有效分隔运行时间并根据基本类似的员工计数将它们分组。图 10 显示了 APM 系统生成的指标树:

图 11 演示了根据员工计数划分的薪水处理运行的时间,它揭示了员工数量和运行时间之间的相互关系:

跟踪程序配置属性允许在属性文件中包括 URL,并能在其中定义范围和阀值(我将简单介绍一下阀值)。属性将在跟踪程序的构造时间被读取,并为 tracer.lookupRange 实现提供后台数据。清单 7 显示了 Payroll Processing 范围的示例配置。我选择使用 java.util.Properties 的 XML 表示,因为它更能兼容奇怪的字符.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
???<comment>Payroll?Process?Range</comment>
???<entry key="L:Payroll Processing">181+ Emps,10:1-10 Emps,50:11-50 Emps,
??????80:51-80 Emps,120:81-120 Emps,180:121-180 Emps</entry>
</properties> 注入外部定义的范围可以使您的应用程序不必频繁更新源代码,这受益于预期的调整和服务水平协议(SLA)在业务方面的变更。当范围和阀值更改生效之后,您只需更新外部文件,而不是应用程序本身。
跟踪阀值和?SLA
外部可配置上下文跟踪的灵活性支持以更加准确和粒度化的方式来定义和测量性能阀值。范围?定义一系列数值区间,可以在其中对测量数据进行分类,而阀值?是对范围的进一步分类,它根据测量数据的确定范围对获取的测量数据进行分类。在分析收集的性能数据时,一个常见的需求是确定和报告执行是 “成功” 还是 “失败”(因为它们未在指定时间发生)。这些数据的总和可以作为关于系统运行健康状况和性能的通用成绩单,或者作为某种形式的 SLA 遵从性评价。?
使用薪水处理系统示例,考虑一个内部服务级目标,它将薪水的执行时间(在定义的员工数范围之内)定义为 Ok、Warn 和 Critical 3 个区间。生成阀值计数的流程从概念上来说非常简单。您只需为跟踪程序提供您认为是各类别各区间的上限运行时间的值,并引导跟踪程序为分类的运行时间发起一个 tracer.traceIncident,然后 — 为简化报告 — 提供一个总数。表 2 显示了一些经过设计的 SLA 运行时间:?
?
表?2.?薪水处理阀值员工数
Ok (ms)
Warn (ms)
Critical (ms)
1-10
280
400
>400
11-50
850
1200
>1200
51-80
900
1100
>1100
81-120
1100
1500
>1500
121-180
1400
2000
>2000
181+
2000
3000
>3000
ITracer API 使用与范围中相同的 XML(属性)文件中定义的值实现了阀值报告。范围和阀值定义在两个方面稍有不同。首先,阀值定义的关键值是一个正则表达式。当 ITracer 在跟踪一个数值时,它会检查阀值正则表达式是否匹配被跟踪指标的复合名称。如果匹配,则阀值会将测量数据分类为 Ok、Warn 或 Critical,并为跟踪附加一个额外的 tracer.traceIncident。其次,由于阀值只定义了两个值(根据定义,Critical 值大于 warn 值),因此配置只由两个数值组成。清单 8 显示了之前介绍的薪水处理 SLA 的阀值配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
???<!-- Payroll Processing Thresholds -->
???<entry key="Payroll Processing.*81-120 Emps.*Elapsed Time \(ms\)">1100,1500</entry>??
???<entry key="Payroll Processing.*1-10 Emps.*Elapsed Time \(ms\)">280,400</entry>??
???<entry key="Payroll Processing.*11-50 Emps.*Elapsed Time \(ms\)">850,1200</entry>??
???<entry key="Payroll Processing.*51-80 Emps.*Elapsed Time \(ms\)">900,1100</entry>?????
???<entry key="Payroll Processing.*121-180 Emps.*Elapsed Time \(ms\)">1400,2000</entry>??
???<entry key="Payroll Processing.*181\+ Emps.*Elapsed Time \(ms\)">2000,3000</entry>??
</properties> 图 12 显示添加了阀值指标的薪水处理的指标树:?
图 12. 添加了阀值的薪水处理指标
图 13 演示了哪些收集的数据可以表示在饼形图中:
?
图 13. 薪水处理的 SLA 汇总(1 到 10 名员工)
确保查找上下文和阀值分类的效率和速度非常重要,因为它
们在完成实际工作的线程中执行。在 ITracer 实现中,所有指标名称在第一次被跟踪程序发现时,将存储在(线程安全)为具备和不具备阀值的指标指定的映射中。当特定指标的跟踪事件发生后,阀值确定过程占用的时间是一个 Map 查找时间,它的速度通常足够快。如果阀值条目或指标名称的数量非常大,则一种合理的解决方案是推迟阀值确定,并在异步跟踪线程池中处理它们。
?
图 9 演示了一个简单的插装截取程序,它通过捕获调用的起始时间来测量它的运行时间,然后将测量数据(运行时间和指标复合名称)分发给处理队列。然后,线程池读取该队列,获取测量数据并完成跟踪流程。
?