Java 理论与实践: 用JMX检测应用程序
数据类型
MBean 中的访问器和操作能够用任何其签名形式的原语类型,以及 String、Date 和其他标准库类。也可以使用这些允许的类型的数组和集合。MBean 方法也可以使用其他可以序列化的数据类型,但是这样做会造成互操作性问题,因为类文件也必须对 JMX 客户机可用。(如果使用 RMI 传输,可以使用 RMI 的自动类下载特性完成这项任务。)如果想在管理接口中使用结构化数据类型,还想避免与类可用性相关的互操作性问题,可以使用 JMX 的开放 MBean 特性来表达复合或表格数据。
检测服务器应用程序
在创建管理接口时,某些参数和操作的特点很自然地就表明这些参数和数据应当被包含在内,例如配置参数、操作统计值、调试操作(例如修改日志级别或把应用程序状态导出到文件)、生命周期操作(启动、停止)。检测一个应用程序,让它支持对这些属性和操作的访问,通常相当容易。但是,要从 JMX 获得最大价值,就要在设计时考虑什么数据在运行时对用户和操作员有用。
如果用 JMX 了解服务器应用程序的工作情况,需要一种标识和跟踪工作单元的机制。如果使用标准的 Runnable 和 Callable 接口描述任务,通过让任务类自描述(例如实现toString() 方法),可以在任务生命周期内跟踪它们,并提供 MBean 方法来返回等候中、处理中和完成的任务列表。
清单 3 中的 TrackingThreadPool 演示的是 ThreadPoolExecutor 的一个子类,它及时给出正在处理中的是哪些任务,以及已经完成的任务的时间统计值。它通过覆盖 beforeExecute() 和 afterExecute() 挂钩,并提供能检索所搜集数据的 getter,实现这些任务。
清单 3. 搜集处理中的任务和平均的任务时间统计值的线程池类
清单 4 中的 ThreadPoolStatusMBean 显示了 TrackingThreadPool 的 MBean 接口,它提供了活动任务、活动线程、完成任务、等候任务的计数,还提供了当前等候执行和正在执行的任务的列表。在管理接口中包含等候和执行任务的列表,让您既可以看到应用程序的工作难度,又可以看到它目前的工作内容。这个特性不仅让您可以洞察应用程序的行为,还能洞察它正在操作的数据集的性质。public class TrackingThreadPool extends ThreadPoolExecutor { private final Map<Runnable, Boolean> inProgress = new ConcurrentHashMap<Runnable,Boolean>(); private final ThreadLocal<Long> startTime = new ThreadLocal<Long>(); private long totalTime; private int totalTasks; public TrackingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); inProgress.put(r, Boolean.TRUE); startTime.set(new Long(System.currentTimeMillis())); } protected void afterExecute(Runnable r, Throwable t) { long time = System.currentTimeMillis() - startTime.get().longValue(); synchronized (this) { totalTime += time; ++totalTasks; } inProgress.remove(r); super.afterExecute(r, t); } public Set<Runnable> getInProgressTasks() { return Collections.unmodifiableSet(inProgress.keySet()); } public synchronized int getTotalTasks() { return totalTasks; } public synchronized double getAverageTaskTime() { return (totalTasks == 0) ? 0 : totalTime / totalTasks; } }
清单 4. TrackingThreadPool 的 MBean 接口
| public interface ThreadPoolStatusMBean { public int getActiveThreads(); public int getActiveTasks(); public int getTotalTasks(); public int getQueuedTasks(); public double getAverageTaskTime(); public String[] getActiveTaskNames(); public String[] getQueuedTaskNames(); } |
如果任务的重量级足够,那么甚至可以再进一步,在每个任务提交时都为它注册一个 MBean (然后在任务完成时再取消注册)。然后可以用管理接口查询每个任务的当前状态、运行了多长时间,或者请求取消任务。
清单 5 中的 ThreadPoolStatus 实现了 ThreadPoolStatusMBean 接口,它提供了每个访问器的明显实现。与 MBean 实现类中的典型情况一样,每个操作实现起来都很细碎,所以把实现委托给了底层受管对象。在这个示例中,JMX 代码完全独立于受管实体的代码。TrackingThreadPool 对于 JMX 一无所知;通过为相关的属性提供管理方法和访问器,它提供了自己的编程管理接口。 还可以选择在实现类中直接实现管理功能(让 TrackingThreadPool 实现 TrackingThreadPoolMBean 接口),或者单独实现(如清单 4 和 5 所示)。
清单 5. TrackingThreadpool 的 MBean 实现
为了演示这些类如何提供对应用程序操作的内容的了解,请考虑这样一个 Web 搜寻应用程序,它把工作分成两类任务:获取远程页面,对页面进行索引。每个任务分别用清单 6 所示的 FetchTask 或 IndexTask 描述。可以创建 ThreadPoolStatus MBean,提供处理这些任务所使用的线程池的管理接口,并把它用 JMX 注册。public class ThreadPoolStatus implements ThreadPoolStatusMBean { private final TrackingThreadPool pool; public ThreadPoolStatus(TrackingThreadPool pool) { this.pool = pool; } public int getActiveThreads() { return pool.getPoolSize(); } public int getActiveTasks() { return pool.getActiveCount(); } public int getTotalTasks() { return pool.getTotalTasks(); } public int getQueuedTasks() { return pool.getQueue().size(); } public double getAverageTaskTime() { return pool.getAverageTaskTime(); } public String[] getActiveTaskNames() { return toStringArray(pool.getInProgressTasks()); } public String[] getQueuedTaskNames() { return toStringArray(pool.getQueue()); } private String[] toStringArray(Collection<Runnable> collection) { ArrayList<String> list = new ArrayList<String>(); for (Runnable r : collection) list.add(r.toString()); return list.toArray(new String[0]); } }
清单 6. Web 搜寻应用程序中使用的 FetchTask 类
| public class FetchTask implements Runnable { private final String name; public FetchTask(String name) { this.name = name; } public String toString() { return "FetchTask: " + name; } public void run() { /* Fetch remote resource */ } } |
当此程序处理每个页面时,可能还会对新任务进行排队以获取这个页面上链接的页面,所以在指定时间内,可能会既有获取任务又有尚未完成的索引任务。能够正确地判断正在处理哪个页面,或者正在等候处理哪个页面,不仅让您可以理解应用程序的性能特征,还可以理解应用程序所操作的数据的特征。
图 4 显示了正在处理 whitehouse.gov 站点的 Web 搜寻程序的快照。从图中可以看到已经获取并索引了主页,程序现在的工作是获取和索引直接从该主页链接出的页面。单击 Refresh 按钮,可以对应用程序的工作流程进行取样,它可以提供许多关于应用程序工作情况的信息,却不需引入大量日志或者在调试器中运行应用程序。

图 4. Web 搜寻应用程序中的活动任务和排队任务
结束语
结合平台内的 JMX 支持和 jconsole JMX 客户机可以提供一种向应用程序添加管理和监视功能的轻松方式。即使是没有具体管理需求的应用程序,为它们构建这些功能也会让您对程序的运行及其所处理的数据的性质获得深入了解,而且不需太多的工作和努力。如果应用程序导出管理接口,此接口让您可以查看它操作的内容,那么您就会更加了解它的运行状态——对它是否按预期的方式工作也会更有信心——而不必求助于额外的工具(例如添加日志代码或使用调试器或分析器)。
0
相关文章