第4章 远程管理
4.1 引言
JMX API使你能通过JMX 连接器远程管理你的资源。JMX连接器使得远端的JMX客户端可以访问MBean服务器。连接器的客户端与服务器本质上输出同样的接口。
JMX连接器由连接客户端和连接服务器组成。连接服务器被附加到MBean服务器上,监听客户端的请求。连接客户端负责建立与服务器之间的连接。连接客户端通常使用一个与连接服务器不同的Java虚拟机,并且运行在不同的机器上。JMX API定义了一个基于RMI的标准连接协议。该协议使你能够让JMX客户端连接到远程的MBean服务器上的MBean,并执行MBean上的操作,就像在本地执行这些操作一样。
Java SE平台提供了一个拆箱即用的方式,即通过JMX API的标准RMI连接器,来远程监控应用。RMI连接器自动将应用暴露给远程管理,而不需要你创建一个专用的远程连接器。用正确的属性启动你的应用,就可以激活远程管理代理。监控和管理应用,与JMX技术是兼容的,也可以远程连接这些应用并监控它们。
4.2 将资源暴漏给远程管理JConsole
如果你使用一个开箱即用的远程管理代理和已经存在的监控和管理工具,例如JConsole,那么使用JMX API将你的应用暴露给远程管理是极其容易的。
为了将你的应用暴露给远程管理,你需要用合适的属性启动你的应用。这个例子演示如何将Main JMX代理暴露给远程管理。
安全性考虑:
这个例子为了简化,禁用鉴权和加密安全机制。然而,当你在真实的环境里实现远程管理的时候,你应该实现这些安全机制。
下一步?指向如何激活安全机制的JMX技术文档
这个例子需要的Java版本:Java SE 6平台。为了远程监控Main JMX代理,按照下面的步骤运行示例:
- 如果这一步还没有做,那就保存JMX例子的压缩包jmx_examples.zip到你的工作目录work_dir。
- 在命令行或者终端使用下面的命令解压缩这个包:unzip jmx_examples.zip。
- 在work_dir目录下编译例子中所有类:javac com/example/*.java。
- 启动你的应用,并指定参数将Main暴露给远程管理。(对于Windows用户,使用^而不是\将一个长命令行分成多行显示):
java -Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
com.example.Main
生成了一条Main正在等待一些事情发生的确认消息。
- 在不同的机器上的命令行或者终端窗口,启动JConsole:jconsole。新建连接对话框将会显示出来,列表中显示出可以本地连接的正在运行的JMX代理
- 选择“远程进程”,输入如下地址:hostname:9999。
- 点击“连接”。Main运行所在的Java虚拟机的当前活动的概览显示出来。
- 点击“MBean”标签。这个面板将会显示所有当前注册到远程MBean服务器中的MBean。
- 在左边的MBean树中,展开com.example节点。你会看到在Main中创建并注册的MBean Hello。如果你点击Hello,你会在MBean树中看到它相关的“属性”和“操作”,即使它运行一个不同的机器上。
- 要想关闭JConsole,选择 连接->退出。
4.3 创建自定义JMX客户端
前面几章讲述了如何创建JMX技术的MBean和MXBean,如何把它们注册到JMX代理。然而,前面所有的例子都使用了现成的JMX客户端JConsole。本章将演示如何创建自定义的JMX客户端。
自定义客户端的例子Client包含在jmx_examples.zip中。这个客户端与前面几章已经看到的相同的MBean、MXBean、JMX代理进行交互。鉴于Client类的大小,我们分成几块来介绍。
4.3.1 导入JMX远程API类
为了能创建运行在JMX客户端并远程连接到JMX代理的连接,你需要使用javax.management.remote包中的一些类。
package com.example;
...
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class Client {...
Client类创建了JMXConnector类的实例,而这个类又需要JMXConnectorFactory工厂类和JMXServiceURL类。
4.3.2 创建通知监听器(Notification Listener)
JMX客户端需要有一个Notification消息的处理器,以便监听和处理由在JMX代理的MBean服务器中注册的MBean发送的通知。JMX客户端的通知处理器是No NotificationListener接口的实例,如下所示:
...
public static class ClientListener implements NotificationListener {
public void handleNotification(Notification notification,
Object handback) {
echo("\nReceived notification:");
echo("\tClassName: " + notification.getClass().getName());
echo("\tSource: " + notification.getSource());
echo("\tType: " + notification.getType());
echo("\tMessage: " + notification.getMessage());
if (notification instanceof AttributeChangeNotification) {
AttributeChangeNotification acn = (AttributeChangeNotification) notification;
echo("\tAttributeName: " + acn.getAttributeName());
echo("\tAttributeType: " + acn.getAttributeType());
echo("\tNewValue: " + acn.getNewValue());
echo("\tOldValue: " + acn.getOldValue());
}
}
}
通知监听器确定它收到的Notification消息的源,获取存储在Notification中的信息。然后根据收到的通知消息的类型执行不同的动作。当监听器收到AttributeChangeNotification类型的通知时,监听器就会通过调用AttributeChangeNotification类的方法getAttributeName、getAttributeType、getNewValue和getOldValue获取发生改变的MBean属性的名字、类型、新的属性值和旧的属性值。
在后面的代码中创建了ClientListener一个实例
ClientListener listener = new ClientListener();
4.3.3 创建RMI连接器客户端
Client类创建了一个RMI连接器客户端,配置RMI连接器客户端以便连接到RMI连接器服务器。RMI连接器服务器是在你启动JMX代理Main的时候启动的。这将允许运行在同一台机器上的JMX客户端与JMX代理进行交互。
public static void main(String[] args) throws Exception {
echo("\nCreate an RMI connector client and " + "connect it to the RMI connector server");
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
正如你所看到的,Client类定义了JMXServiceURL类的对象url,这个url表示连接器客户端期望能找到连接服务器的位置。URL允许连接器客户端从运行在本机端口9999的RMI注册表中检索RMI连接器服务器的存根jmxrmi,并连接到RMI连接器服务器。
识别了RMI注册表,连接器客户端就可以创建了。连接器客户端jmxc是通过工厂类JMXConnectorFactory的connect()方法创建的的一个JMXConnector接口的实例。调用connect()方法时传入了两个参数url和null。
4.3.4 连接到远程的MBean服务器
RMI连接就位后,JMX客户端必须连接到远程的MBean服务器,这样它就可以通过J远程的JMX代理与不同的MBean进行交互了。
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
然后通过调用JMXConnector实例jmxc的方法getMBeanServerConnection()就可以创建了MBeanServerConnection的实例mbsc。
现在连接器客户端已经连接到了由JMX代理创建的MBean服务器了,可以注册MBean,执行MBean的操作,因为连接对于两端来说都是透明的。
首先,客户端定义了一些简单的操作来发现MBean的信息:
...
echo("\nDomains:");
String domains[] = mbsc.getDomains();
Arrays.sort(domains);
for (String domain : domains) {
echo("\tDomain = " + domain);
}
...
echo("\nMBeanServer default domain = " + mbsc.getDefaultDomain());
echo("\nMBean count = " + mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> names = new TreeSet<ObjectName>(mbsc.queryNames(null, null));
for (ObjectName name : names) {
echo("\tObjectName = " + name);
}
...
客户端调用MBeanServerConnection的不同方法来获得不同的MBean运行所在的域名,在MBean服务器中注册的MBean数量,以及每一个MBean的对象名(ObjectName)。
4.3.5 通过代理执行远程的MBean的操作
客户端通过MBean服务器连接创建MBean代理来访问MBean服务器上的Hello MBean。对于客户端来说,这个MBean代理是本地的,但模仿了远程的MBean。
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName, HelloMBean.class, true);
echo("\nAdd notification listener...");
mbsc.addNotificationListener(mbeanName, listener, null, null);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
mbeanProxy.setCacheSize(150);
echo("\nWaiting for notification...");
sleep(2000);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
echo("\nInvoke sayHello() in Hello MBean...");
mbeanProxy.sayHello();echo("\nInvoke add(2, 3) in Hello MBean...");
echo("\nadd(2, 3) = " + mbeanProxy.add(2, 3));
waitForEnterPressed();
MBean代理允许你通过Java接口访问MBean,允许你只调用代理的方法,而不必写一段冗长的代码来访问远程的MBean。在这里,通过调用javax.management.JMX类的getnewMBeanProxy()静态方法创建Hello的MBean代理,该方法需要传入MBeanServerConnection连接mbsc、对象名mbeanName、MBean接口的class类型、布尔值true,这个布尔值意味着代理的行为就像NotificationBroadcaster一样。JMX客户端可以执行Hello定义的操作,就像它们是本地注册的MBean的操作一样。JMX客户端也添加了一些通知监听器,改变MBean的CacheSize属性,就会发送一个Notification通知。
4.3.6 通过代理执行远程的MXBean的操作
用同样的方式,你也可以创建MXBean的代理。
ObjectName mxbeanName = new ObjectName ("com.example:type=QueueSampler");
QueueSamplerMXBean mxbeanProxy = JMX.newMXBeanProxy(mbsc, mxbeanName, QueueSamplerMXBean.class);
QueueSample queue1 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue1.getDate());
echo("QueueSample.Head = " + queue1.getHead());
echo("QueueSample.Size = " + queue1.getSize());
echo("\nInvoke clearQueue() in QueueSampler MXBean...");
mxbeanProxy.clearQueue();
QueueSample queue2 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue2.getDate());
echo("QueueSample.Head = " + queue2.getHead());
echo("QueueSample.Size = " + queue2.getSize());
如上所示,创建MXBean的代理,只需要调用JMX.newMXBeanProxy()方法就可以了。MXBean代理mxbeanProxy允许客户端触发QueueSample MXBean的操作,就像它们是本地注册的MXBean的操作一样。
4.3.7 关闭连接
一旦JMX客户端获取了它所需要的所有信息,并且执行了远程服务器的MBean所有的操作,连接必须被关闭
jmxc.close();
调用JMXConnector.close()方法关闭连接
4.3.8 运行自定义的客户端示例
这个示例需要Java SE 6以上的版本。使用自定义的JMX客户端Client监控JMX代理Main,按照下面的步骤执行:
- 如果这一步还没有做,那就保存JMX例子的压缩包jmx_examples.zip到你的工作目录work_dir。
- 在命令行或者终端使用下面的命令解压缩这个包:unzip jmx_examples.zip。
- 在work_dir目录下编译例子中所有类:javac com/example/*.java。
- 启动Main,并指定参数将Main暴露给远程管理
java -Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
com.example.Main
- 生成了一条Main正在等待一些事情发生的确认消息。
- 在不同的终端或命令行窗口启动Client客户端:java com.example.Client
显示MBeanServerConnection已经被获取的确认消息。 - 敲回车(Enter)。注册到MBean服务器并由Main启动的所有MBean所在的域名都显示出来了。
- 再一次敲回车(Enter)。在MBean服务器中注册的MBean的数量以及这些MBean的对象名都显示出来了。显示的MBean包括所有运行在Java虚拟机中的标准平台MXBean,以及在通过Main注册到MBean服务器中的Hello MBean和QueueSampler MXBean。
- 再一次敲回车(Enter)。Client触发Hello MBean的所有操作,结果如下:
- Notification监听器被添加到Client中以监听来自Main的Notification消息。
- CacheSize的值由200改为150。
- 在启动Main的终端或命令行窗口,显示CacheSize属性值被修改的确认消息。
- 在启动Client的终端或命令行窗口,来自Main的通知消息显示出来,通知Client客户端CacheSize的值已经发生改变。
- Hello MBean的sayHello()方法被触发。
- 在启动Main的终端或命令行窗口,显示出“Hello World”消息。
- Hello MBean的add()方法被触发,入参为2和3。在Client客户端显示出了返回结果。
- 再一次敲回车(Enter)。Client触发QueueSampler MXBean的所有操作,结果如下:
- QueueSampler的值date、head和size显示出来。
- clearQueue()操作被触发。
- 再一次敲回车(Enter)。Client客户端关闭与MBean服务器的连接,一条确认消息显示出来。