天天看点

spring boot的JMX原理

JMX

所谓JMX,是Java Management Extensions的缩写,从官方的文档上来看,他就是一个框架,和JPA、JMS是一样的,和我们平时使用的Spring、Hibernate也没有什么区别。就是通过将监控和管理涉及到的各个方面的问题和解决办法放到一起,统一设计,以便向外提供服务,以供使用者调用,它的API在以下两个地方:

  • java.lang.management:
  • javax.management.*:包括javax.management.loading、javax.management.modelmbean等;
spring boot的JMX原理

资源管理

资源可以是:

  • 硬件设备
  • 计算机网络
  • 操作系统
  • 运行服务器

监控是为了及时发现问题,以便能够及时提出正确的解决方案,避免损失;管理是为了预防问题的发生,同时也是为了使资源能够得到有效的利用,使利益最大化。在监控和管理的时候要考虑的方面如下:

  • 监控硬件和平台的运行情况:包括服务器和操作系统等;
  • 合理配置资源:比如内存的配置是否合理,CPU是否足够强大;
  • 收集应用运行的情况:比如说访问量多大,响应时间是否够快,哪个地区的访问人数最多;
  • 在应用发生异常时能够及时定位问题所在:这是监控的核心之一;

JMX的术语

  • 管理资源(Manageable resource):像我在上面说的,只要是能帮助是你的活动和系统正常运转的都算资源,可以是硬件、也可以是应用,只要能够被Java的类描述即可;
  • 管理组件(MBean,managed bean):从资源的角度来看,它是一个对抽象的资源的一个描述,比如说如果资源是数据库,管理组件中可以提供数据库的一些描述信息,比如数据库服务器的运行地址、端口,类型以及最大连接数等等,但是这个类必须满足JMX规范中的提出的要求,比如命名规则和实现标准,类似于JavaBean。由于管理组件是资源的抽象,所以管理应用是直接面向MBean,也就说MBean会被暴露给管理应用来操作和访问,通过MBean中提供的属性和方法,MBean也有几种类型,为了不添堵,如果没有特殊说明,本文指的都是Standard MBean,关于它的具体使用在下面的编码部分说明;
  • 管理组件服务器(MBean Server):简单的来看,它是一个容器,用来盛装和管理一组MBeans,它是整个JMX管理环境的核心,由于其中有很多的MBean,所以它必须提供一种机制来区分各个MBean,这就是注册机制,每个添加到MBean Server的MBean在注册的时候都要提供一个ObjectName来区分彼此,MBean Server 通过这个ObjectName来查找每个MBean,在JMX中是通过ObjectName类来为每个MBean提供唯一的一个标识,它包括两部分:
  • 域名:这个域名通常是和想要注册到的MBean Server的名称标识相同,以便根据功能模块区分不同MBean Server中的MBean;
  • 键值对列表:被用来唯一的标识MBean,也提供了关于该MBean的信息,形式如下:HelloAgent:name=helloWorld;其中的属性不一定是真实的MBean的属性,仅仅要求当和其他的MBean比较的时候能够唯一标识,每个ObjectName中都要至少有一个属性;当ObjectName重复的时候,注册的时候会抛出javax.management.InstanceAlreadyExistsException,在后面的编码阶段会着重说明这一点;
  • JMX代理(JMX Agent):它提供一系列的服务来管理一系列的MBeans,它是MBean Server的容器。JMX代理提供一些服务,包括创建MBean之间的关系,动态加载类,简单监视服务,以及计时器;代理可以有一系列的协议适配器(Protocol adapters )和连接器(connectors ),协议适配器和连接器也是Java类,通常情况下也是MBeans,这些适配器和连接器是提供转接功能而存在的,以便可以在远程使用不同的协议,通过客户端与这个代理连接,它内部可以映射到一个外部的协议或者暴露代理给远程连接,这就意味着JMX代理可以被一系列不同的管理协议和工具使用,在本质上是插件式架构的一种体现,体现了可插拔的思想;
  • 协议适配器和连接器(Protocol adapters and connectors ):协议适配器和连接器是JMX Agent中的对象,将代理暴露给不同的管理应用和协议,这个和不同的数据库的驱动程序类似,每个数据库都有自己的一套协议来联系,为了保持进行连接,就需要在JDBC应用和数据库服务器之间通过不同的驱动程序关联。一个JMX Agent可以有任意数量的适配器和连接器;它们也是MBeans;
  • 通知(Notification ):通知是由MBeans和MBean Server 提出的,其中封装了具体的事件和相应的数据。其他的MBeans或者Java对象可以注册作为监听器来接收这些通知,其实就是观察者设计模式在JMX中的应用;

JMX架构图

spring boot的JMX原理

从图中我们可以看到,JMX的结构一共分为三层:

1、基础层:主要是MBean,被管理的资源。

MBean分为如下四种,我接下来主要介绍standard MBean

spring boot的JMX原理

2、适配层:MBeanServer,主要是提供对资源的注册和管理。

3、接入层:提供远程访问的入口。

接下来我这里会用程序来介绍三种访问JMX的方式:

JDK的小工具Jconsole访问

首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀(这个规范很重要)

package jmx;


public interface HelloMBean
{
     public String getName();


     public void setName(String name);


     public String getAge();


     public void setAge(String age);


     public void helloWorld();


     public void helloWorld(String str);


     public void getTelephone();
}      

定义一个实现类,实现上面的接口:

1 package jmx;
 2 
 3 /*
 4  * 该类名称必须与实现的接口的前缀保持一致(即MBean前面的名称
 5  */
 6 public class Hello implements HelloMBean
 7 {
 8     private String name;
 9         
10     private String age;
11 
12     public void getTelephone()
13     {
14         System.out.println("get Telephone");
15     }
16 
17     public void helloWorld()
18     {
19         System.out.println("hello world");
20     }
21 
22     public void helloWorld(String str)
23     {
24         System.out.println("helloWorld:" + str);
25     }
26 
27     public String getName()
28     {
29         System.out.println("get name 123");
30         return name;
31     }
32 
33     public void setName(String name)
34     {
35         System.out.println("set name 123");
36         this.name = name;
37     }
38 
39     public String getAge()
40     {
41         System.out.println("get age 123");
42         return age;
43     }
44 
45     public void setAge(String age)
46     {
47         System.out.println("set age 123");
48         this.age = age;
49     }      
53 }      

定义agent层:

package jmx;


import java.lang.management.ManagementFactory;


import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;


public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
{
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
         ObjectName helloName = new ObjectName("jmxBean:name=hello");
         //create mbean and register mbean
         server.registerMBean(new Hello(), helloName);
         Thread.sleep(60*60*1000);
    }
}      

这样,一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作。

1、首先在自己的本地路径下:C:\Program Files (x86)\Java\jdk1.6.0_43\bin找到jconsole.exe这个小工具,双击打开:

spring boot的JMX原理

2、双击打开我们的本地进程:HelloAgent:

spring boot的JMX原理

3.在这个界面上,我们可以给程序中HelloMBean的属性赋值,也可以调用其中的方法:

spring boot的JMX原理

4、控制台打印如下:

spring boot的JMX原理

通过JMX提供的工具页访问

这里,我们复用上面的接口和实现类,只需要改动适配层,这里需要到导入外部jar包jdmk

package jmx;


import java.lang.management.ManagementFactory;


import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;


import com.sun.jdmk.comm.HtmlAdaptorServer;


public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
{
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
         ObjectName helloName = new ObjectName("jmxBean:name=hello");
         //create mbean and register mbean
         server.registerMBean(new Hello(), helloName);
         
         ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082");   
         HtmlAdaptorServer adapter = new HtmlAdaptorServer();   
         server.registerMBean(adapter, adapterName);  
         adapter.start();
    }
}      

我们访问地址:http://localhost:8082,点击name=hello:

spring boot的JMX原理
spring boot的JMX原理

1、在这里创建一个AdaptorServer,这个类将决定MBean的管理界面,这里用最普通的Html型界面。AdaptorServer其实也是一个MBean。 

2、我们可以看到这个工具页,其实与我们上一个案例中的Jconsole中的管理界面类似,都可以操作资源中的属性和方法。

通过客户端程序进行远程访问

这里需要对agent进行修改,增加ip和porta绑定部分的逻辑

package jmxTest;


import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;


import javax.management.JMException;
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 HelloAgent
{
    public static void main(String[] args) throws JMException, NullPointerException
    {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("jmxBean:name=hello");
        //create mbean and register mbean
        server.registerMBean(new Hello(), helloName);
        try
        {
            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(9999);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            JMXServiceURL url = new JMXServiceURL
                  ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
            JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
            System.out.println("begin rmi start");
            jcs.start();
            System.out.println("rmi start");
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
          }}      

写到这里,如果没有client进行远程连接,可以使用Jconsole进行远程访问:

spring boot的JMX原理

客户端Client程序,用于与agent进行远程连接:

package jmx;


import java.io.IOException;


import javax.management.Attribute;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
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 IOException, Exception, NullPointerException
    {
        JMXServiceURL url = new JMXServiceURL
            ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
        JMXConnector jmxc = JMXConnectorFactory.connect(url,null);


        MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
        //ObjectName的名称与前面注册时候的保持一致
        ObjectName mbeanName = new ObjectName("jmxBean:name=hello");


        System.out.println("Domains ......");
        String[] domains = mbsc.getDomains();


        for(int i=0;i<domains.length;i++)
        {
            System.out.println("doumain[" + i + "]=" + domains[i] );
        }


        System.out.println("MBean count = " + mbsc.getMBeanCount());
        //设置指定Mbean的特定属性值
        //这里的setAttribute、getAttribute操作只能针对bean的属性
        //例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀
        mbsc.setAttribute(mbeanName, new Attribute("Name","杭州"));
        mbsc.setAttribute(mbeanName, new Attribute("Age","1990"));
        String age = (String)mbsc.getAttribute(mbeanName, "Age");
        String name = (String)mbsc.getAttribute(mbeanName, "Name");
        System.out.println("age=" + age + ";name=" + name);


        HelloMBean proxy = MBeanServerInvocationHandler.
            newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);
        proxy.helloWorld();
        proxy.helloWorld("migu");
        proxy.getTelephone();
        //invoke调用bean的方法,只针对非设置属性的方法
        //例如invoke不能对getName方法进行调用
        mbsc.invoke(mbeanName, "getTelephone", null, null);
        mbsc.invoke(mbeanName, "helloWorld",
            new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"});
        mbsc.invoke(mbeanName, "helloWorld", null, null);
    }
}      

client的控制台打印结果:

spring boot的JMX原理

SpringBoot集成JMX监控

1、首先新建一个SpringBoot工程,并生成类似如下的启动类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}      

2、编写暴露的MBean

import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
@Component
@ManagedResource(objectName = "com.tang.jmx:type=SimpleBean", description = "这里是描述")
public class SimpleBean {
  private long id;
  private String name;
  private int age;
 
  public void setId(long id) {
    this.id = id;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public void setAge(int age) {
    this.age = age;
  }
 
  /**
   * 这里编写暴露方法
   */
  @ManagedOperation(description = "这里是操作")
  public String display() {
    ObjectMapper mapper = new ObjectMapper();
    try {
      return mapper.writeValueAsString(this);
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }
  }
 
}      

3、编写一个外部调用接口,用来低5步模拟内存数据发生变化

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class JmxController {
    @Autowired
    private SimpleBean simpleBean;
 
    @GetMapping("/jmx")
    public SimpleBean simpleBean(@RequestParam(required = true) Long id,
                                 @RequestParam(required = true) String name,
                                 @RequestParam(required = true) Integer age
                                 ){
    simpleBean.setId(id);
    simpleBean.setName(name);
    simpleBean.setAge(age);
    return simpleBean;
    }
}      

4、在application.properties配置文件中配置开启jmx

spring.jmx.enabled=true      

5、启动程序,并以GET请求调用接口:

http://127.0.0.1:8080/jmx?id=1&name=xxx&age=22      

6、启动VisualVM工具,并attach到上述进程中,并找到MBeans属性页,查看jmx暴露的监控bean信息:

spring boot的JMX原理