【jmx】JMX最佳實踐與詳解

九師兄發表於2020-12-13

在這裡插入圖片描述

1.概述

轉載:https://www.iteye.com/blog/shift-alt-ctrl-2404103

2.JMX最佳實踐

1、Object Names(物件命名)

每個JMX MBean都需要一個“Object Name”,選擇一個始終一致的、可用的Object Names將是非常重要的;客戶端與你的物件模型(model)互動時,或許是直接將它展示且可讀,比如jconsole;或者它被應用程式使用Object Names直接訪問你宣告的物件模型。所以,在多個models之間,MBean的物件命名一個儘可能唯一

2、Object Name語法格式

一個Object Name是“javax.management.ObjectName”的例項,Object Name可以是一個MBean的名字,也可以是一個“表示式”、匹配多個MBeans名稱。

它看起來像這樣: “domain:key-property-list(關鍵屬性列表)”,比如“com.demo.model:type=myType,name=myModel”。其中“domain”原則上可以為任意字串,如果domain值為空,它將使用MBean Server的名稱作為預設值;如果domain中包含“*”、“?”正規表示式字元,這些字串只能用來匹配(訪問、查詢、過濾)MBeans,不能用來宣告一個MBean例項。此外domain不能包含“:”特殊字元,因為這是“domain”與“propery-list”的分隔符。

key-property-list”有一個或者多個關鍵屬性組成,格式為“key=value”,比如“type=Thread”;當多個properties是,它們之間以“,”分割,比如“name=DGC,type=Thread”;需要注意,空格是有意義的,我們不需要在“,”之後使用空格,比如“type=Thread, name=DGC”,那麼“ name”將會被認為是屬性名稱。

此外,key的名稱是有字元限制的,我們建議使用JAVA允許的、合法的識別符號。value的字元也是有限制的,對於有些特殊字元,我們需要進行轉義:ObjectName.quote;預設情況下,如果value是字串(不是數字),我們應該總是轉義它,除非我們能夠確定它不會出現特殊字元。比如下述兩個Object Names包含特殊字元(需要轉義):

com.demo.myproject:type=Whatsit,name="25"

com.demo.myproject:type=Whatsit,name="25,26"

第二種情況,如果不轉義將被認為是非法的。

對於表示式,“key-property-list”可以與上述的格式一樣,也可以包含比如“*”、“?”甚至空字串(與“*”等同),可以為“,*”結尾表示一個列表等。最終,這種格式用於匹配多個Object Names,它們具有指定的確切關鍵屬性(如果有)、加上任何其他關鍵屬性。比如“*:type=Thread,*”可以匹配“somedomain:type=Thread”、“somedomain:type=Thread,name=DGC”

3、Object Name約定(規範)

1)一個MBean的Object Name應該是可以“預測的”,這意味著一個可以成為MBean的物件,我們應該能夠知道它的Object Name將會什麼。名稱中不應該包含那些不是該物件固有部分的屬性;此外,同一個物件的name不應該在application不同執行階段而改變,比如不應該包含像“JVM程式ID”、“MBean Server ID”等這些每次執行會變化的屬性。(“同一物件”的改變並不總是清晰或者有意義的,不過如果是這樣,那麼同一個物件始終具有相同的名稱)

2)domain部分,應該以此物件的package名稱作為字首,以避免來自不同子系統(模組)所引入的衝突;比如:

com.demo.project1:type=Whatsit,name=5

org.demo.project2:typpe=Whatsit

domain不應該包含“/”,此字串為MBean Server層次結構(級聯)保留的,將會在後續版本中進行解釋。

3)對於指定“type”的Object Name,應該包含相同的關鍵屬性列表且它們具有相同的語法,且value具有相同語義。

如果在特定domain中,對於指定“type”的MBean只允許一個例項,那麼它通常不再需要除“type”之外的、其他額外的屬性。

如果同一“type”中可能有多個例項,它們可以通過使用不同的domain(即package名)或者其他關鍵屬性來區分。大部分情況下,domain是一樣的,我們通過使用不同的“name”屬性值。

4)最常用的關鍵屬性為:name和type。

通常“type=X,name=Y”組合屬性對一個MBean命名就足夠了;一些JMX感知的控制檯能夠使用比其他名稱更容易閱讀的簡寫形式來顯示這種形式的名稱。

有時候,我們也可以宣告額外的屬性,來滿足上述種正規表示式匹配查詢MBean的情況。比如“category”、“group”等。

4、Object containment(遏制)

有些託管物件(MBean)在邏輯上被其他託管物件所包含,這些物件或許無法獨立存在。此時,下述模式是可行的。假如Server物件包含Application物件,Application包含WebModule物件,WebModule又包含Servlet;那麼它們的名稱看起來是這樣的:

domain:type=Server,name=server5

domain:type=Server.Application,Server=server5,name=app1

domain:type=Server.Application.WebModule,Server=server5,Application=app1,name=module3

domain:type=Server.Application.WebModule.Servlet,Server=server5....WebModule=module3,name=servlet1

這種分層級的“type”屬性可以讓我們在不需要預知Model的前提下即可理解其他keys的含義;由於Object Name中key是無序的,因此無法知道例如在這裡的第三個和第四個名稱中,Application是否包含在Server中,反之亦然。這種模式意味著如果一個指定type的物件包含在其他type的物件中,那麼它應該總是被包含。比如Server.Application總是包含在Server中。如果有其他Application沒有包含在Server物件中,那麼他們應該是不同的type,即事實上它們的type屬性不應該為“Server.Application”;這與“特定type的MBean總是隱含相同key屬性列表”的規則是一致的。

二、MBean介紹

MBean,即“managed bean”(託管的Bean),與普通的javaBean元件一樣,只不過為JMX實現而設計;一個MBean可以表示一個“device”、“Application”或者任何需要被託管的資源。MBean可以暴露一些管理介面:

1)一系列可讀、寫的屬性。(getter、setter)

2)一些可以被執行的operation。

3)自描述資訊

管理介面,在MBean例項的整個生命週期中無法被修改;此外,MBean在遇到預定義的事件(events)發生時也可以觸發通知(notification)。JMX實現中宣告瞭5種MBean:

1)Standard MBeans

2)Dynamic MBeans

3)Open MBeans

4)Model MBeans

5)MXBeans

1、Standard MBeans(標準MBeans)是最常用的MBean,通常我們通過宣告一個“SomethingMBean”樣式的java介面、以及一個“Something”的類實現此介面的方式來建立一個MBean。此介面中方法用於宣告一個屬性或者操作,屬性和操作的方法都遵循一定的設計規則。一個標準的MBean,由MBean介面和實現類組合而成;介面宣告一系列暴露的屬性和操作,實現類用於提供功能特性。

MBean介面

以HelloMBean為例:

package com.example;   
   
public interface HelloMBean {   
   
    public void sayHello();   
    public int add(int x, int y);   
      
    public String getName();   
       
    public int getCacheSize();   
    public void setCacheSize(int size);   
}  
 

MBean介面採用其JAVA實現類的名稱、並以“MBean”作為字尾。如示例,介面名為HelloMBean,那麼其實現類為Hello。

根據JMX實現規範,一個MBean介面由可讀寫(readable、writable)的、命名化和型別化的屬性,還有一些可以被Application呼叫的操作組成。HelloMBean介面表述了2個操作:add()和sayHello;2個屬性:只讀的name屬性、可讀寫的cacheSize屬性;getter和setter方法允許託管的Application訪問或者修改屬性的值。根據JMX實現,getter是任何public方法、返回值型別不是void、且其命名以get開頭;getter允許manager讀取屬性值,返回值物件的型別即為屬性的型別。setter是任何public方法、只包含一個引數、且其命名以set開頭,setter允許manager寫入屬性的新值,其型別與引數型別一致。

MBean實現

public class Hello ...   
    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) {  
        this.cacheSize = size;   
        ....  
    }   
}  
 

建立JMX Agent來管理資源

一旦資源以MBean方式表達,那麼該資源將有JMX Agent來管理。JMX Agent核心元件為MBean Server,MBean Server是一個託管物件用於MBean註冊;JMX Agent還包含一些管理MBeans的服務。請參見MBean Server API。

import java.lang.management.*;   
import javax.management.*;   
   
public class Main {   
   
    public static void main(String[] args)   
        throws Exception {   
       
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();   
        ObjectName name = new ObjectName("com.example:type=Hello");   
        Hello mbean = new Hello();   
        mbs.registerMBean(mbean, name);   
            
        ...  
       
        System.out.println("Waiting forever...");   
        Thread.sleep(Long.MAX_VALUE);   
    }   
}   
 

此main方法為示例,用於獲取有Platform(通常為JVM平臺、框架元件)已經建立或者初始化的MBean Server示例,通過呼叫getPlatformMBeanServer()方法。如果Platform尚未建立或者初始化MBean Server,那麼此方法將會自動建立建立一個MBean Server例項(通過MBeanServerFactory.createMBeanServer,一旦建立此後即可公用)。

此後建立一個ObjectName例項,每個JMX MBean都必須有一個object name,其為ObjectName型別的例項,其命名語法必須符合JMX規範。即,必須包含一個domain和關鍵屬性列表(K-V);通常domain為package名稱,“type”為類名,“name”為資源名稱。

建立一個Hello例項,並將其使用objectName註冊到MBean Server中,通過MBeanServer.registerMBean()方法。註冊之後,即可通過JMX 管理操作呼叫Hello例項的operation和屬性。我們可以使用jconsole來演示MBean的屬性獲取和操作呼叫。

2、MXBeans

MXBean是一種特殊的MBean,它僅引用預定義資料型別。通過這種方式,你的MBean可以被任何Client使用,包括remote客戶端,不需要訪問表示此MBean的模型特定(model-specific)的類。MXBean提供了一種將相關值繫結在一起的便捷方式,無需客戶端專門配置來處理繫結包。

與Standard MBean一樣,MXBean的定義通過宣告一個SomethingMXBean介面和一個實現類,不過MXBean的實現類的名稱不需要必須為Something。MXBean中的每個方法宣告一個屬性或者操作。@MXBean註釋也可以修飾在任何已有的JAVA介面上,從而不需要專門開發一個以“MXBean”為字尾的介面。

MXBean背後的主要思想是MXBean介面中引用的諸如“java.lang.management.MemoryUsage”之類的型別,比如“java.lang.management.MemoryMXBean”,對映成標準的型別,稱為“Open Types”定義在“javax.management.openmbean”包中。具體的對映規則請參見MXBean規範實現。不過,通用原則是對於簡單型別比如String、int等保持不變,對於複雜型別比如MemoryUsage型別將會對映成標準型別“CompositeDataSupport”。

有關MBean和MXBean的區別,請參考:https://docs.oracle.com/javase/8/docs/api/javax/management/MXBean.html

MXBean介面

package com.example;   
   
public interface QueueSamplerMXBean {   
    public QueueSample getQueueSample();   
    public void clearQueue();   
}  
    

MXBean實現

package com.example;   
   
import java.util.Date;   
import java.util.Queue;   
   
public class QueueSampler   
                implements QueueSamplerMXBean {   
       
    private Queue<String> queue;   
           
    public QueueSampler (Queue<String> queue) {   
        this.queue = queue;   
    }   
           
    public QueueSample getQueueSample() {   
        synchronized (queue) {   
            return new QueueSample(new Date(),   
                           queue.size(), queue.peek());   
        }   
    }   
           
    public void clearQueue() {   
        synchronized (queue) {   
            queue.clear();   
        }   
    }   
}  
 

宣告覆雜型別

package com.example;   
   
import java.beans.ConstructorProperties;   
import java.util.Date;   
   
public class QueueSample {   
       
    private final Date date;   
    private final int size;   
    private final String head;   
           
    @ConstructorProperties({"date", "size", "head"})   
    public QueueSample(Date date, int size,   
                        String head) {   
        this.date = date;   
        this.size = size;   
        this.head = head;   
    }   
           
    public Date getDate() {   
        return date;   
    }   
           
    public int getSize() {   
        return size;   
    }   
           
    public String getHead() {   
        return head;   
    }   
}  
 

在QueueSample類中,MXBean框架通過所有的getter方法來將此例項轉換為CompositeData例項(陣列);(在反序列化時)使用@ConstructorProperties註釋再從CompositeData例項中重建QueueSample例項。

JMX Agent中註冊MXBean的方式與MBean一樣,沒有任何區別。

簡單來說,如果你的MBean中的屬性均為簡單型別,用Standard MBean即可;如果有複雜型別,比如自定義的類,那麼需要使用MXBean,並由CompositeData將複雜型別的屬性進行轉換。(複雜型別也是由JAVA的簡單型別組合而成)。

JAVA已經內建了多個MXBean實現,可以幫助大家來學習JMX的相關技術。

1、BufferPoolMXBean:有關“direct”、“mapped” buffer的資源資訊;如果Application為網路IO系統(比如Netty程式設計)、或者有大量檔案操作,你應該考慮關注此MXBean。

2、ClassLoadingMXBean:有關JVM類載入相關的資源資訊;如果Application為序列化相關的元件、指令碼化整合元件、有較多代理類(包括動態載入,OSGI)等,你應該關注此MXBean。

3、GarbageCollectorMXBean:有關JVM GC相關的資源,包括GC時長、GC次數和相關記憶體狀態。

4、MemoryPoolMXBean:有關JVM中“記憶體池”的相關資源資訊,可以配合MemoryManagerMXBean一起使用。一個Application中可能有多個“記憶體池”例項,我們可以通過MemoryManagerMXBean獲取記憶體池的列表,並檢視此記憶體池的存量和GC相關資訊。

5、OperatingSystemMXBean:有關作業系統的相關資源資訊,比如CPU負載等。

6、PlatformManagedObject:內部介面,所有的JAVA平臺有關的MXBean都擴充套件此介面,比如上述幾個MXBean;通常應用程式不應該實現它。

7、RuntimeMXBean:有關runtime的資訊,比如VM的引數、版本等。

8、ThreadMXBean:有關執行時執行緒狀態的資源資訊,比如“CPU高耗執行緒”、“死鎖執行緒”等,可以幫助我們優化併發操作等。

三、JMX程式碼樣例

我們可以參考很多開源元件的JMX實現,比如tomcat-jdbc等,如下示例僅供參考,我們建議JMX MBean註冊、登出操作應該在MBean實現類中,此外也要求大家在開發元件時,儘量增加MBean資訊託管,這對我們探測元件執行時狀態資料非常有效,而且對監控良好。

/** 
 * Description 
 * <p> 
 * </p> 
 * DATE 17/12/9. 
 * 
 * @author liuguanqing. 
 */  
public class SlowQuery implements SlowQueryMXBean{  
  
    private String name;  
    public SlowQuery() {  
  
    }  
  
    public SlowQuery(String name) {  
        this.name = name;  
    }  
  
    protected void registerJmx() {  
        try {  
            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();  
            ObjectName objectName = getObjectName();  
            if(mBeanServer.isRegistered(objectName)) {  
                return;  
            }  
            mBeanServer.registerMBean(this, objectName);  
        } catch (MalformedObjectNameException e) {  
            //ObjectName不合法時,建議統一ObjectName.quote(string)  
        } catch (RuntimeOperationsException e) {  
            //JMX operation操作異常時  
        } catch (MBeanException e) {  
            //其他異常  
        } catch (InstanceAlreadyExistsException e) {  
            //如果相同名稱的MBean已註冊  
        } catch (NotCompliantMBeanException e) {  
            //如果MBean無法被JMX Agent無法相容  
        } catch (Exception e) {  
            //  
        }  
    }  
  
    protected ObjectName getObjectName() throws Exception{  
        Class clazz  = getClass();  
        if(name == null) {  
            name = clazz.getSimpleName();  
        }  
        return new ObjectName(clazz.getPackage().getName() + ":type=" + clazz.getSimpleName() + ",name=" + name);  
    }  
  
    protected void deRegisterJmx() {  
        try {  
            ManagementFactory.getPlatformMBeanServer().unregisterMBean(getObjectName());  
        } catch (MBeanRegistrationException e) {  
            //  
        } catch (InstanceNotFoundException e) {  
            //  
        } catch (MalformedObjectNameException e) {  
            //  
        } catch (RuntimeOperationsException e) {  
            //  
        } catch (Exception e) {  
            //  
        }  
  
    }  
}  
 

四、Spring與JMX(簡述)

Spring中接入JMX的方式比較多,我們僅描述一種常用的、易於實施的方式。具體參見:Spring與JMX。

1、宣告MBean,基於Spring註釋

import org.springframework.jmx.export.annotation.ManagedAttribute;  
import org.springframework.jmx.export.annotation.ManagedOperation;  
import org.springframework.jmx.export.annotation.ManagedOperationParameter;  
import org.springframework.jmx.export.annotation.ManagedResource;  
import org.springframework.stereotype.Component;  
  
/** 
 * Description 
 * <p> 
 * </p> 
 * DATE 17/12/9. 
 * 
 * @author liuguanqing. 
 */  
//@ManagedResource(objectName = "com.demo.web.MyApp:type=HttpRequestGlobalInfo,name=httpRequestGlobal")  
@Component  
@ManagedResource  
public class HttpRequestGlobalInfo {  
  
    private long totalRequestTimes;//請求總次數  
    private long avgResponseTime;//平均響應時間  
  
    @ManagedAttribute()  
    public long getTotalRequestTimes() {  
        return totalRequestTimes;  
    }  
  
    public void setTotalRequestTimes(long totalRequestTimes) {  
        this.totalRequestTimes = totalRequestTimes;  
    }  
  
    @ManagedAttribute()  
    public long getAvgResponseTime() {  
        return avgResponseTime;  
    }  
  
    public void setAvgResponseTime(long avgResponseTime) {  
        this.avgResponseTime = avgResponseTime;  
    }  
  
    @ManagedOperation  
    public void reset(@ManagedOperationParameter(name = "all",description = "all")boolean all) {  
        if(all) {  
            totalRequestTimes = 0;  
        }  
        avgResponseTime = 0;  
    }  
  
    @ManagedOperation  
    public void reset() {  
        reset(false);  
    }  
}  
 

1)此Bean需要為SpringBean,我們可以通過@Component或者@Resource宣告,當然你也可以在Spring.xml配置。

2)MBean上必須使用@ManagedResource註釋,此後Spring將會通過代理 + 自動掃描的方式(配置註釋驅動)來建立MBean例項以及註冊到MBean Server。

ManagedResource註釋中,可以宣告objectName,此值建議遵循上述種JMX規範。如果不指定objectName和相關引數,那麼將會採用“domain”(來自配置檔案)作為域,“name”屬性為SpringBean名稱(預設值為類名簡寫,首字母小寫,比如:httpRequestGlobalInfo)、“type”屬性為“類名簡寫”(比如HttpRequestGlobalInfo)。

通常有兩種選擇:

A)你可以統一在spring.xml中指定domain,@ManagedResource中不再指定objectName;這種方式也利於remote Client來統一操作,因為這些MBean都在一個domain中。

B)你希望自定義domain(比如package名作為domain),那麼你可以在@ManagedResource中指定objectName。(推薦方式)

3)@ManagedAttribute,宣告在屬性的getter或者setter方法上。主要是Spring與JMX Agent相容(代理類)。

4)@ManagedOperation,宣告在操作方法上,表示此方法問MBean的operation。(否則,只認為是普通的java方法,不會被export)

5)@ManagedOperationParameter,可以宣告在operation的方法上,也可以宣告在方法的引數上,用於表示此operation允許的引數列表。(代理類)

2、spring.xml配置

<bean id="jmxExporter" class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter" lazy-init="false">  
    <!-- 需要人工註冊的、非Spring實現的MBean,比如dataSource -->  
    <property name="beans">  
        <map>  
            <entry  
                    key="org.apache.tomcat.jdbc.pool.jmx:name=dataSourceMBean,type=ConnectionPool"  
                    value="#{datasource.getPool().getJmxPool()}"/>  
        </map>  
    </property>  
    <!-- 對於Spring註釋驅動的MBean,將自動載入 -->  
    <!-- 此處domain,僅供示例展示,通常建議在@ManagedResource中指定objectName -->  
    <property name="defaultDomain" value="Application" />  
</bean>  
<!-- 如果你沒有開啟Component-scan,那麼你需要宣告MBean作為springBean -->  
 

3、JMX Agent

最常用的Agent就是jconsole工具,你可以執行此UI,檢視MBean的所有資訊。不過在Production環境中,或許remote埠無法訪問,jconsole則無法使用,我們建議大家的WEB專案可以基於jolokia元件作為嵌入式的JMX Agent。接入jolokia也是非常簡單,請參見:jolokia與Spirng JMX監控

文件參考:

1、JMX最佳實踐:http://www.oracle.com/us/technologies/java/best-practices-jsp-136021.html

2、ObjectName:https://docs.oracle.com/javase/1.5.0/docs/api/javax/management/ObjectName.html

3、MBean介紹:https://docs.oracle.com/javase/tutorial/jmx/mbeans/index.html

4、JVM內部MXBean:https://docs.oracle.com/javase/8/docs/api/java/lang/management/package-summary.html

相關文章