在项目中经常用到JMX的service url,一直都没有完全理解它的用法,下面本文以RMI方式为例,分别从JMX server和JMX client两方面入手,简单的说明一下:
service:jmx:rmi://hostname1:port1/jndi/rmi://hostname2:port2/jmxrmi
比如有以上url,JMX server和JMX client都会用到这个URL,但它针对JMX server和JMX client含义是有区别的,在继续本文前,将JMX的处理流程讲一下:
1.JMX server会向RMI Registry注册服务,并在RMI Registry中存放RMI存根(RMI存根中存放有JMX服务的hostname和port)
2.JMX client会到RMI Registry上查询服务,如果查找到,则将RMI存根传给JMX client
3.JMX client拿到RMI(Remote Method Invocation,远程方法调用)存根后,就会使用它连上JMX server进行通讯(或者称作RMI方法调用)
对于JMX serverservice:jmx:rmi
表示JMX服务使用JRMP协议进行通讯,JRMP协议是RMI通讯的标准协议
hostname1
这是一个不会被用到的hostname,就算是写了也会被忽略掉,所以一般不用写(但后面的反斜线不能省)。如果hostname没有用,这里就出现了两个问题:
1.JMX client怎么知道JMX服务的连接地址?
2.如果JMX server在代理/防火墙后,或者JMX server有多个网卡,那么JMX client怎么才能知道如何与JMX服务通讯?
对于1,RMI Registry一般会将JMX服务的默认hostname或者ip传给JMX client
对于2,JMX server提供了一个系统参数指定代理/防火墙的hostname/IP,或者可访问的网卡的hostname/IP,指定方式如下:
-Djava.rmi.server.hostname=proxy_hostname,此时,RMI Registry会将这个hostname/IP传给JMX client
port1
这是JMX server提供的用于通讯的端口,如果不写,JMX server会随机分配一个;这个端口会在JMX client访问RMI Registry后返回给JMX client,JMX client拿到这个端口就会使用此端口与JMX server正式通讯了。如果JMX client需要通过代理/防火墙访问JMX server,这个端口也是代理/防火墙的端口。
jndi
使用jndi服务对JMX服务进行注册
rmi://hostname2:port2
说明要注册到RMI Registry上,RMI Registry的访问地址为rmi://hostname2:port2。其中hostname2:port2是RMI Registry所在的主机名和端口号,由于RMI Registry只可能与JMX server在同一台机器上,则hostname2只能是JMX server的主机名(或者其中一个主机名)或本机IP(或者其中一个本机IP)。
jmxrmi
RMI Registry中注册的服务名叫jmxrmi;jmxrmi也是JMX服务的默认注册名;用户可以设置自己的注册名
这里有一个令人疑惑的地方,怎么是用jndi api去注册JMX的服务呀?我们知道rmi服务一般是通过rmi api注册的,比如:
Naming.bind("rmi://hostname2:port2/jmxrmi", new Stub()); 原来,jndi api可以适配到rmi api做注册,只要提供rmi的ContextFactory就行,上面的代码等价于:
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); env.put(Context.PROVIDER_URL, "rmi://hostname2:port2/"); Context ictx = new InitialContext(env); ictx.bind("jmxrmi", new Stub());
对于JMX clientservice:jmx:rmi
表示将使用JRMP协议进行通讯
hostname1:port1
对JMX client没有任何用处,因为要访问的JMX服务的hostname和port都是RMI Registry返回的,所以一般都不写
jndi
JMX client使用jndi服务去查找JMX服务
rmi://hostname2:port2/jmxrmi
jndi服务内部是到RMI Registry上去查找服务的,RMI Registry的访问地址是hostname2:port2,查找服务的注册名为jmxrmi;当查找成功后,返回信息中会包含JMX服务对外的hostname和port,JMX client使用这个hostname和port就可以和JMX server上的JMX服务通讯,进行RMI方法调用了。
JMX client例子
import javax.management.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 Client {
public static void main(String[] args) throws Exception {
// Create an RMI connector client and
// connect it to the RMI connector server
JMXServiceURL url =
new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:1099/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
// Create a dedicated proxy for the MBean instead of
// going directly through the MBean server connection
HelloMBean mbeanProxy =
JMX.newMBeanProxy(mbsc, mbeanName, HelloMBean.class, true);
mbeanProxy.setCacheSize(150);
mbeanProxy.sayHello();
jmxc.close();
}
}
public interface HelloMBean {
// management attributes
public String getName();
public void setName(String name);
// management operations
public void print();
}
//在这个Class里,有一个隐含attributes: name, 提供了set和get的方法,同时有一个操作方法print():
//再定义一个concrete类:
public class Hello implements HelloMBean {
private String name = "";
public String getName() {
return name;
}
public void setName(String name) {
}
public void print() {
System.out.println("Hello, " + name + "!!" );
}
}
JMX server例子
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
public class Main {
public static void main(String[] args) throws Exception {
int rmiPort = 1099;
Registry registry = LocateRegistry.createRegistry(rmiPort);
// Get the Platform MBean Server
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Construct the ObjectName for the Hello MBean we will register
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
// Create the Hello World MBean
Hello mbean = new Hello();
// Register the Hello World MBean
mbs.registerMBean(mbean, mbeanName);
String url = "service:jmx:rmi://localhost:1010/jndi/rmi://localhost:" + rmiPort + "/jmxrmi";
JMXServiceURL jmxUrl = new JMXServiceURL(url);
JMXConnectorServer jmxConnServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, null, mbs);
jmxConnServer.start();
}
}
JMX常用参数 如果要使用JVM默认提供的JMX服务,并且关闭认证和SSL连接安全功能,则需要设置如下3项:
# 设置远程访问端口 com.sun.management.jmxremote.port=portNum # 关闭认证功能 com.sun.management.jmxremote.authenticate=false # 关闭SSL com.sun.management.jmxremote.ssl=false
如果自己开发程序创建JMX服务,如“JMX server例子”中那样,就不需要使用以上参数,否则会报错。
其它有用的参数如下:
# 客户端多长时间接收不到响应就超时 sun.rmi.transport.tcp.responseTimeout=5000
# 客户端checker线程发起心跳请求的时间间隔,如果为0则表示关闭心跳线程 jmx.remote.x.client.connection.check.period=5000
# 服务器端Timeout线程超时时间 jmx.remote.x.server.connection.timeout=30000
参考文献:
Monitoring and Management Using JMX Technology: http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html
JMX tutorial: https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/tutorial/tutorialTOC.html
Java RMI: https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/
sun.rmi Properties: https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/sunrmiproperties.html
java.rmi Properties: https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/javarmiproperties.html
jndi-rmi http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-rmi.html