天天看點

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原理