1. 程式人生 > >【轉】開源框架是如何通過JMX來做監控的(一)

【轉】開源框架是如何通過JMX來做監控的(一)

相信很多做Java開發的同學都使用過JDK自帶的 jconsole 或者 jvisualvm 監控過JVM的執行情況,但不知道有沒有留意過它們會有一個MBean的功能/標籤,通過MBean可以看到在JVM中執行的元件的一些屬性和操作

    例如,可以看到Tomcat 8080埠Connector的請求連線池資訊,Druid資料庫連線池的activeCount連線數以及連線池配置資訊,這些開源框架或中介軟體都是通過JMX的方式將自己的一些管理和監控資訊暴露給我們

    JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴充套件。這種機制可以方便的管理正在執行中的Java程式。常用於管理執行緒,記憶體,日誌Level,服務重啟,系統環境等。

    下面簡單介紹一下JMX和其最簡單的Standard MBean,以及常用例項,之後的文章中會分析Druid資料庫連線池等開源框架是如何通過JMX做監控的,沒準在我們的程式中也可以用到。

    以下是本文的目錄大綱:

    若有不正之處請多多諒解,歡迎批評指正、互相討論。

    請尊重作者勞動成果,轉載請標明原文連結:

一、JMX架構及基本概念

從上面的架構圖可以看到JMX主要分三層,分別是:

1、裝置層(Instrumentation Level)

主要定義了資訊模型。在JMX中,各種管理物件以管理構件的形式存在,需要管理時,向MBean伺服器進行註冊。該層還定義了通知機制以及一些輔助元資料類。

裝置層其實就是和被管裝置通訊的模組,對於上層的管理者來說,Instrumentation 就是裝置,具體裝置如何通訊,是採用SNMP,還是採用ICMP,是MBean的事情。

該層定義瞭如何實現JMX管理資源的規範。一個JMX管理資源可以是一個Java應用、一個服務或一個裝置,它們可以用Java開發,或者至少能用Java進行包裝,並且能被置入JMX框架中,從而成為JMX的一個管理構件(Managed Bean),簡稱MBean。管理構件可以是標準的,也可以是動態的,標準的管理構件遵從JavaBeans構件的設計模式;動態的管理構件遵從特定的介面,提供了更大的靈活性。

在JMX規範中,管理構件定義如下:它是一個能代表管理資源的Java物件,遵從一定的設計模式,還需實現該規範定義的特定的介面。該定義了保證了所有的管理構件以一種標準的方式來表示被管理資源。

管理介面就是被管理資源暴露出的一些資訊,通過對這些資訊的修改就能控制被管理資源。一個管理構件的管理介面包括:

    1) 能被接觸的屬性值

    2) 能夠執行的操作

    3) 能發出的通知事件

    4) 管理構件的構建器

本文著重介紹最基本也是用的最多的Standard Mbean,至於其它的如Dynamic MBean、Model MBean暫時不介紹,請參考本文最後資料中的文章。

Standard MBean是最簡單的MBean,它管理的資源必須定義在介面中,然後MBean必須實現這個介面。它的命名也必須遵循一定的規範,例如我們的MBean為Hello,則介面必須為HelloMBean。

2、代理層(Agent Level)

Agent層 用來管理相應的資源,並且為遠端使用者提供訪問的介面。Agent層構建在裝置層之上,並且使用並管理裝置層內部描述的元件。Agent層主要定義了各種服務以及通訊模型。該層的核心是 MBeanServer,所有的MBean都要向它註冊,才能被管理。註冊在MBeanServer上的MBean並不直接和遠端應用程式進行通訊,他們通過 協議介面卡(Adapter) 和 聯結器(Connector) 進行通訊。通常Agent由一個MBeanServer和多個系統服務組成。JMX Agent並不關心它所管理的資源是什麼。

3、分佈服務層(Distributed Service Level)

分佈服務層關心Agent如何被遠端使用者訪問的細節。它定義了一系列用來訪問Agent的介面和元件,包括Adapter和Connector的描述。

二、Standard MBean

Standard MBean的設計和實現是最簡單的,它們的管理介面通過方法名來描述。Standard MBean的實現依靠一組命名規則。這些命名規則定義了屬性和操作。

一個只讀屬性在MBean中只有get方法,既有get又有set方法表示是一個可讀寫的屬性。

為了實現Standard MBean,必須遵循一套繼承規範。必須為每一個MBean定義一個介面,而且這個介面的名字必須是其被管理的資源的物件類的名稱後面加上”MBean”,之後把它們註冊到MBeanServer中就可以了

MBean介面:

1

2

3

4

5

6

public interface HelloMBean {

public String getName();

public void setName(String name);

public String printHello();

public String printHello(String whoName);

}

接下來是真正的資源物件,因為命名規範的限制,因此物件名稱必須為Hello

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public class Hello implements HelloMBean {

private String name;

@Override

public String getName() {

return name;

}

@Override

public void setName(String name) {

this.name = name;

}

@Override

public String printHello() {

return "Hello "+ name;

}

@Override

public String printHello(String whoName) {

return "Hello  " + whoName;

}

}

接下來建立一個HelloMBean,並將其註冊到MBeanServer:

1

2

3

4

5

6

7

8

9

10

11

12

13

public class HelloAgent {

public static void main(String[] args) throws Exception {

// 首先建立一個MBeanServer,MBeanServer用來管理我們的MBean,通常是通過MBeanServer來獲取我們MBean的資訊

MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 

String domainName = "MyMBean";

// 為MBean(下面的new Hello())建立ObjectName例項

ObjectName helloName = new ObjectName(domainName+":name=HelloWorld");

// 將new Hello()這個物件註冊到MBeanServer上去

server.registerMBean(new Hello(),helloName);

}

}

三、通過RMI方式連線JMX Server

繼續上面的HelloMBean、HelloAgent,我們可以通過RMI的方式註冊URL來提供客戶端連線,這樣就可以通過 jvisualvm 或者 自己寫的Java程式作為JMX的客戶端來管理MBean

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

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 HelloAgent {

public static void main(String[] args) throws Exception {

//create mbean server

MBeanServer server = ManagementFactory.getPlatformMBeanServer();

//create object name

ObjectName objectName = new ObjectName("jmxBean:name=hello");

//create mbean and register mbean

server.registerMBean(new Hello(), objectName);

/**

* JMXConnectorServer service

*/

//這句話非常重要,不能缺少!註冊一個埠,繫結url後,客戶端就可以使用rmi通過url方式來連線JMXConnectorServer

Registry registry = LocateRegistry.createRegistry(1099);

//構造JMXServiceURL

JMXServiceURL jmxServiceURL = new JMXServiceURL();

//建立JMXConnectorServer

JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, server); 

//啟動

cs.start();

}

}

在建立JMXConnectorServer時建立的JMXServiceURL比較複雜,但其實其完整版為:

service:jmx:rmi://localhost:0/jndi/rmi://localhost:1099/jmxrmi

藍色部分可以省略掉

service:jmx:    這個是JMX URL的標準字首,所有的JMX URL都必須以該字串開頭,否則會拋MalformedURLException

rmi:    這個是jmx connector server的傳輸協議,在這個url中是使用rmi來進行傳輸的

localhost:0    這個是jmx connector server的IP和埠,也就是真正提供服務的host和埠,可以忽略,那麼會在執行期間隨意繫結一個埠提供服務

jndi/rmi://localhost:1099/jmxrmi    這個是jmx connector server的路徑,具體含義取決於前面的傳輸協議。比如該URL中這串字串就代表著該jmx connector server的stub是使用 jndi api 繫結在 rmi://localhost:1099/jmxrmi 這個地址

如果在伺服器端,我們用該URL建立一個jmx connector server,則大概流程如下:

1、將jmx connect server內部的server物件的rmi stub export到本地的一個隨機埠(也可以自己指定),接收外部連線

2、通過jndi api將該stub繫結在rmi://localhost:6000/jmxrmi這個地址上,這需要在本地的1099埠上執行著一個rmiregistry,如果不存在則會丟擲異常

如果在客戶端,我們通過該URL建立一個connector,則大概流程如下:

1、訪問1099埠,通過jndi api到rmi://localhost:1099/jmxrmi這個地址取回stub

2、stub中已經包含了真實伺服器的IP和埠,所以可以直接根據該stub連線到真實的伺服器

下面具體展示一段客戶端程式碼,可以獲取jmx connector,並展示HelloMBean的一些資訊,屬性,及呼叫其方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

import java.util.Iterator;

import java.util.Set;

import javax.management.Attribute;

import javax.management.MBeanInfo;

import javax.management.MBeanServerConnection;

import javax.management.MBeanServerInvocationHandler;

import javax.management.ObjectInstance;

import javax.management.ObjectName;

import javax.management.remote.JMXConnector;

import javax.management.remote.JMXConnectorFactory;

import javax.management.remote.JMXServiceURL;

public class JMXClient {

public static void main(String[] args) throws Exception { 

//connect JMX 

JMXConnector jmxc = JMXConnectorFactory.connect(url,null); 

MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();      

ObjectName mbeanName = new ObjectName("jmxBean:name=hello");    

//print domains 

System.out.println("Domains:---------------"); 

String domains[] = mbsc.getDomains(); 

for (int i = 0; i < domains.length; i++) {        

System.out.println("Domain[" + i +"] = " + domains[i]);     

}    

System.out.println();

//MBean count 

System.out.println("MBean count:---------------"); 

System.out.println("MBean count = " + mbsc.getMBeanCount());

System.out.println();

//process attribute 

System.out.println("process attribute:---------------");

mbsc.setAttribute(mbeanName, new Attribute("Name", "newName")); //set value 

System.out.println("Name = " + mbsc.getAttribute(mbeanName, "Name")); //get value 

System.out.println();

//invoke via proxy 

System.out.println("invoke via proxy:---------------");

HelloMBean proxy = (HelloMBean) MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);         

System.out.println(proxy.printHello()); 

System.out.println(proxy.printHello("zhangsan"));

System.out.println();

//invoke via rmi 

System.out.println("invoke via rmi:---------------");

System.out.println(mbsc.invoke(mbeanName, "printHello", null, null));          

System.out.println(mbsc.invoke(mbeanName, "printHello", new Object[] { "lisi" }, new String[] { String.class.getName() }));    

System.out.println();

//get mbean information 

System.out.println("get mbean information:---------------");

MBeanInfo info = mbsc.getMBeanInfo(mbeanName);          

System.out.println("Hello Class:" + info.getClassName());       

System.out.println("Hello Attribute:" + info.getAttributes()[0].getName());      

System.out.println("Hello Operation:" + info.getOperations()[0].getName());    

System.out.println();

//ObjectName of MBean 

System.out.println("ObjectName of MBean:---------------");        

Set set = mbsc.queryMBeans(null, null); 

for (Iterator it = set.iterator(); it.hasNext();) { 

ObjectInstance oi = (ObjectInstance)it.next();         

System.out.println(oi.getObjectName());         

jmxc.close();      

}   

}

例子中我們可以get/set HelloMBean的Name屬性,可以通過先獲取代理和直接RMI的方式呼叫HelloMbean的printHello()方法等。

這樣我們就有了一個完整的JMX server、client的例子,很多開源框架(如一些資料庫連線池DBCP2、Druid)都實現了JMX來達到對其自身的監控,雖然這不是唯一的方法,也可以通過其它方式提供監控查詢介面,但由於JMX是sun提出的通用標準,故大家紛紛響應實現。所以當我們使用這些開源框架並希望對其執行狀況做一些管理監控時,可以採用JMX的方式獲取其暴露出的MBean相關屬性和方法。之後會分析一些Druid是如何通過JMX的方式做管理監控的。

四、通過VisualVM連線JMX Server

開啟JDK自帶的VisualVM,由於本例是本地localhost的JMX server,那麼在左側欄“本地”上右鍵,建立JMX連線

點選後只需填寫本地的RMIRegistry註冊的埠1099即可,當然也可以填寫完整的URL:service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi

連線成功後,就可以看到HelloMBean以及其提供的屬性和操作,還可以呼叫printHello()方法

參考資料: