Thursday, March 04, 2010

JMX, JVisualVM

监控对系统来说是个非常重要的功能,比如如果你的系统能够显示一个动态变化的每10秒刷新一次的实时销量趋势图,客户一定会非常开心,事实上这个功能也是非常重要的,不是一个玩具。
我最近一直在想这个问题,因为这段时间维护系统开始从一个新的角度去看系统开发,开始更多的考虑系统可维护性的问题(对于多数开发者他们只关注开发阶段的问题,但是一个软件系统的生命周期中90%都处于维护阶段),其中监控维护者(系统监控)和客户(偏重业务监控)都需要的工具。
自然而然的我就想到了JMX,这个技术已经出现很长时间了,有成熟的规范,有丰富的资料。但是我仍然再想为什么要用JMX来实现监控呢? 有什么优势?我自己定义web service,也一样可以暴露监控和管理接口,如果这样来做那就是轻车熟路,技术上没有门槛。而JMX,对于一套专属的系统来说,规范有多少意义,这些接口只有管理系统可以看到,我宁愿采用定制的,轻型的,一切自己掌控的方案。 如果说因为这是JCP的规范,所以我们应该采用JMX,老实说,理由很牵强。
但是我还是看了一遍sun的(O,oracle的)JMX tutorial,现在我想我有了其他的理由,这些理由比JCP更有说服力。
1)与JVisualVM的直接整合。
2)JDK自带的,无需第三方包
3)很容易,学习曲线非常低

1。 与JVisualVM的直接整合
这是一个很酷的特性,我直接开始想到真实系统的应用场景。系统管理员可以用JVisualVM来监控和管理系统,而定制开发的web系统来管理业务数据,这样,我们只需要写服务器端的代码,而客户端就直接是JVisualVM,简单轻松。看看我实验的代码(来自sun jmx tutorial)

* 定义一个StandardMBean(JMX规范中,接口名以MBean结束,比如XXXMBean,而对应的实现类直接去掉MBean就可以了,约定优于配置)
package org.jmx;
public interface HelloMBean {
public void sayHello();
public int add(int x, int y);
public String getName();
public int getCacheSize();
public void setCacheSize(int size);
}


package org.jmx;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
public void sayHello() {
System.out.println("hello, world");
}
public int add(int x, int y) {
return x + y;
}
public String getName() {
return this.name;
}
public int getCacheSize() {
return this.cacheSize;
}
public synchronized void setCacheSize(int size) {
int oldSize = this.cacheSize;
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
Notification n = new AttributeChangeNotification(this, sequenceNumber++, System
.currentTimeMillis(), "CacheSize changed", "CacheSize", "int", oldSize,
this.cacheSize);
sendNotification(n);
}

@Override
public MBeanNotificationInfo[] getNotificationInfo() {
String[] types = new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE };
String name = AttributeChangeNotification.class.getName();
String description = "An attribute of this MBean has changed";
MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description);
return new MBeanNotificationInfo[] { info };
}

private final String name = "Reginald";
private int cacheSize = DEFAULT_CACHE_SIZE;
private static final int DEFAULT_CACHE_SIZE = 200;
private long sequenceNumber = 1;
}


在实现类中继承了NotificationBroadcasterSupport ,这样可以向注册的listener发送通知,后来可以看到我从客户端注册了一个listener,这样服务器端发送通知之后,客户端就可以收到(好像在JVisualVM的场景中没有什么作用,但是自定义客户端则非常重要)。

* 启动Server端
package org.jmx;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class Main {
public static void main(String[] args) throws Exception {

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); //1
ObjectName name = new ObjectName("com.example.mbeans:type=Hello");
Hello mbean = new Hello();
mbs.registerMBean(mbean, name); //2
System.out.println("Waiting forever..."); //3
Thread.sleep(Long.MAX_VALUE);
}
}


其中 //1 是获得平台platformMBeanServer, JVisualVM会连接到这个MBeanServer,当然也可以通过MBeanServerFactory来创建自己的MBeanServer。
//2 注册MBean
//3 main thread进入sleep状态,RMI connector server应该是有一个新的线程

* 客户端实现
package org.jmx;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class MyJConsole {
public static void main(String args[]) {
try {
// "jmxrmi" is the default service name registered by PlatformMBeanServer, which can connected
// by JVisualVM and JConsole directly.
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi:///jndi/rmi://localhost:4949/jmxrmi"); //1
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

// register a listener
System.out.println("Register a remote listener.");
ObjectName name = new ObjectName("com.example.mbeans:type=Hello");
mbsc.addNotificationListener(name, new HelloListener(), null, null); //2

System.out.println("Waiting forever...");
Thread.sleep(Long.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
}


//1 实例化一个JMXServiceURL , 这个url的格式可以参考JMXServiceURL 的javadoc。
//2 注册客户端listener到远程MBeanServer。 注意所有的类如果实现了Remote和NotificationListener接口,那么在远程传输的时候是传引用而不是值,所以这里listener的逻辑都会在client执行,不是在server端执行。

* client listener
package org.jmx;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationListener;

public class HelloListener implements NotificationListener {

@Override
public void handleNotification(Notification notification, Object handback) {
if (notification instanceof AttributeChangeNotification){
AttributeChangeNotification attNotification = (AttributeChangeNotification)notification;
System.out.println("change CacheSize from " + attNotification.getOldValue()
+ " to " + attNotification.getNewValue());
}
else {
System.out.println(notification);
}
}
}


开始执行这些代码。
1) 启动server端
>> java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=4949 -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.40.0.101
-Dcom.sun.management.jmxremote.authenticate=false org.jmx.Main (还有制定classpath一类的)
* 注意这里的-Djava.rmi.server.hostname=10.40.0.101, 实际上client跑在32bit的win7操作系统上,而server是64bit的linux操作系统, 如果不指定这个属性来绑定4949端口到network interface 10.40.0.101上,那么client可能无法连接到server。 因为client的机器配置为只能访问server的内网ip(10.40.0.101),所以必须绑定server端的VM在10.40.0.101上监听4949端口。
Waiting forever...

2)启动客户端
>> java org.jmx.MyJConsole
Register a remote listener.
Waiting forever...

3) 启动JVisualVM,从左边窗口可以看到一个‘Local’节点,右键点击,选择‘add JMX connection’, 在conntion中输入‘service:jmx:rmi:///jndi/rmi://localhost:4949/jmxrmi’。你会看到
新建的这个节点,双击来展开右边的tab pane, 选择‘MBeans,然后你就可以看到自己注册的MBean ’com.example.mbeans:type=Hello‘。 双击节点’Hello‘,在右边选择’Attributes‘ ,并且编辑’cacheSize‘的值为444。
然后来检查server和client的控制台有什么输出。
Server: Cache size now 444
Client: change CacheSize from 333 to 444


怎么样? 还是很值得在项目中采用JMX的吧!


Now I finished a example which use Spring to expose JMX to JvisualVM, my test environment: tomcat7.0.29 + spring2.5.6
1) Define Spring in web.xml:

xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">

Tomcat7





contextConfigLocation
classpath:spring-service.xml



org.springframework.web.context.ContextLoaderListener



2) Implement a simple POJO:
package org.tomcat7;

public class HelloBean {
private int age;
private String name;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void append(String suffix) {
this.name += suffix;
}
}

3) Export HelloBean by Spring(spring-service.xml):

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">


lazy-init="false">













4) Deploy web application to tomcat and launch it.
NOTE: modify catelina.sh to enable remote JMX connection(-Dcom.sun.management.jmxremote.port=3333 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false )

5) Start JvisualVM, and connect to localhost:3333.
Now from 'MBean' tab, you will find our HelloBean, then you can moniter states and invoke operations of it.