JMX整理

jopen 10年前發布 | 129K 次閱讀 JMX Java開發

What and Why JMX

JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴展。這種機制可以方便的管理正在運行中的Java程序。常用于管理線程,內存,日志Level,服務重啟,系統環境等。

試想,一個正在運行中的程序,我們如果想改變程序中的一些屬性,可以通過什么方法呢?可能有這么幾個方法:

  • 對于服務器式的程序,可以制作管理頁面,通過HTTP post與servlet來更改服務器端程序的屬性。

    </li>

  • 對于服務器式的程序,還可以通過SOAP方式。但這需要程序開啟了SOAP端的服務。

    </li>

  • 可以使用RMI遠程調用。但這需要設計開啟RMI服務。

    </li>

  • 如果是SWT或Swing的程序,則可以通過設計UI管理界面,使用戶可以和程序內部交互。

    </li>

  • 還有一種方式,是將可改變的屬性放入配置文件XML,properties或數據庫,程序輪詢配置文件,以求獲取最新的配置。

    </li> </ul>

    上面幾個方法都是常見,但卻無法通用的。所謂通用,是指解決方案符合一個標準,使得任何符合此標準的工具都能解析針對此標準的方案實現。這樣A公司設計的方案,B公司可以根據標準來解析。JMX就是Java管理標準。

    JMX的構成

    JMX由三部分組成:

    1. 程序端的Instrumentation, 我把它翻譯成可操作的儀器。這部分就是指的MBean. MBean類似于JavaBean。最常用的MBean則是Standard MBean和MXBean.

      </li>

    2. 程序端的JMX agent. 這部分指的是MBean Server. MBean Server則是啟動與JVM內的基于各種協議的適配器。用于接收客戶端的調遣,然后調用相應的MBeans.

      </li>

    3. 客戶端的Remote Management. 這部分則是面向用戶的程序。此程序則是MBeans在用戶前投影,用戶操作這些投影,可以反映到程序端的MBean中去。這內部的原理則是client通過某種協議調用agent操控MBeans. 

      </li> </ol>

      JMX agent與Remote Management之間是通過協議鏈接的,這協議可能包含:

      • HTTP

        </li>

      • SNMP

        </li>

      • RMI

        </li>

      • IIOP

        </li> </ul>

        JMX agent中有針對上面協議的各種適配器。可以解析通過相應協議傳輸過來的數據。Remote Management client則可以用現成的工具,如JConsole, 也可以自己書寫java code。

        接下來,我們看是一步一步,通過代碼示例來熟悉JMX各種特性。

        受監管的程序

        JMX是用于管理java程序的,為了試驗,我們首先需要寫一個小程序Echo。然后加入JMX對此程序進行監管。這個程序就是每隔10秒鐘,輸出一個預先定義好的Message。

        首先定義Message類。

        public class Message {
            private String title, body, by;
            
            public Message() {
                title="none";
                body="none";
                by="none";
            }
            
            public String getTitle() {
                return title;
            }
            
            public void setTitle(String title) {
                this.title = title;
            }
            
            public String getBody() {
                return body;
            }
            
            public void setBody(String body) {
                this.body = body;
            }
            
            public String getBy() {
                return by;
            }
            
            public void setBy(String by) {
                this.by = by;
            }
            
            public void echo() {
                System.out.println("<"+title+">");
                System.out.println(body);
                System.out.println("by " + by);
            }
        }

        定義Echo類

        public class Echo {
            public static Message msg = new Message();
            public static boolean running=true;
            public static boolean pause=false;
            
            public static void main(String[] args) {
                // 開啟JMX Agent。如果不需要JMX,只是單獨運行程序,請屏蔽掉下面這行代碼。
                new MessageEngineAgent().start();
                
                while(running) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (!pause) msg.echo();
                }
            }
        }

        執行Echo,得到每過10秒鐘,則會輸出一個消息:

        <none>

        none

        by none

        </blockquote>

        MBean

        接下來,開始設計管理程序的MBean. 在設計MBean之前,必須要先了解MBean都包括哪幾種。MBean包含:

        1. Standard MBean

          </li>

        2. Dynamic MBean

          </li>

        3. Open MBean

          </li>

        4. Model MBean

          </li>

        5. MXBean

          </li> </ol>

          最常用最簡單的兩個就是Standard MBean與MXBean. 首先搞清楚,MBean和MXBean的區別是什么。

          Standard MBean與MXBean的區別

          這里有一些細節,列出了兩只的區別http://docs.oracle.com/javase/7/docs/api/javax/management/MXBean.html 。它們最根本的區別是,MXBean在Agent與Client之間會將自定義的Java類型轉化為Java Open Type. 這樣的好處是Client無需獲取MXBean的接口程序,便可訪問和操作MXBean的投影。如果使用MBean, client則必須先將MBean的接口程序放到classpath中,否則無法解析MBean中自定義類型。

          基于上述原因,我將使用MXBean做為例子。實際上,JVM自帶的幾乎全是MXBean。

          實現

          定義MXBean的接口,注意命名規則,必須以MXBean結尾。

          public interface MessageEngineMXBean {
              //結束程序。
              public void stop();
              //查看程序是否暫停。
              public boolean isPaused();
              //暫停程序或者繼續程序。
              public void pause(boolean pause);
              public Message getMessage();
              //修改message
              public void changeMessage(Message m);
          }

          實現部分。

          public class MessageEngine implements MessageEngineMXBean {
              private final Message message = Echo.msg;
              
              @Override
              public void stop() {
                  Echo.running = false;
              }

              @Override     public boolean isPaused() {         return Echo.pause;     }

              @Override     public void pause(boolean pause) {         Echo.pause = pause;     }

              @Override     public Message getMessage() {         return this.message;     }

              @Override     public void changeMessage(Message m) {         this.message.setBody(m.getBody());         this.message.setTitle(m.getTitle());         this.message.setBy(m.getBy());     } }</pre>

          Notification

          在JMX中,還有一個重要的概念是Notification。構成Notification的幾個接口是:

          1. NotificationEmitter, 只要實現此接口,就可以發出Notification和訂閱Notification. 類NotificationBroadcasterSupport則實現了NotificationEmitter.

            </li>

          2. NotificationListener, 實現此接口的可以訂閱JMX的Notification。

            </li>

          3. Notification, 消息本身。

            </li> </ol>

            修改MessageEngine, 使它在pause的時候發送通知給訂閱者。我把修改的部分貼上。

            public class MessageEngine extends NotificationBroadcasterSupport implements MessageEngineMXBean {
                private long sequenceNumber = 1;
                ... ...
                ... ...
                public MessageEngine() {
                    addNotificationListener(new NotificationListener() {
                        @Override
                        public void handleNotification(Notification notification, Object handback) {
                            System.out.println("*** Handling new notification ***");
                            System.out.println("Message: " + notification.getMessage());
                            System.out.println("Seq: " + notification.getSequenceNumber());
                            System.out.println("*********************************");
                        }
                    }, null, null);
                }
                ... ...
                ... ...
                @Override
                public void pause(boolean pause) {
                    Notification n = new AttributeChangeNotification(this,
                            sequenceNumber++, System.currentTimeMillis(),
                            "Pause changed", "Paused", "boolean",
                            Echo.pause, pause);
                    Echo.pause = pause;
                    this.sendNotification(n);
                }
                ... ...
                ... ...
                // 規定可以發送的Notification Type,不在Type list中的Notification不會被發送。
                @Override
                public MBeanNotificationInfo[] getNotificationInfo() {
                    String[] types = new String[]{
                        AttributeChangeNotification.ATTRIBUTE_CHANGE
                    };
                    
                    String name = AttributeChangeNotification.class.getName();
                    String description = "An attribute of this MBean has changed";
                    MBeanNotificationInfo info = 
                            new MBeanNotificationInfo(types, name, description);
                    return new MBeanNotificationInfo[]{info};
                }
            }

            Client端如何使用Notification,可以查看后面的Client一節。

            JMX Agent

            如果說Agent只是被Local使用,比如本地的JConsole,只需要開啟MBeanServer,并注冊MBean即可。不需要配置協議適配器。但如果需要遠程管理,比如遠程的JConsole或者自定義的管理器,則還需要配置兩者相互打交道的協議適配器。

            public class MessageEngineAgent {
                public void start() {
                    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
                    try {
                        ObjectName mxbeanName = new ObjectName("com.example:type=MessageEngine");
                        MessageEngineMXBean mxbean = new MessageEngine();
                        mbs.registerMBean(mxbean, mxbeanName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            因為java默認自帶的了JMX RMI的連接器。所以,只需要在啟動java程序的時候帶上運行參數,就可以開啟Agent的RMI協議的連接器。

            java -Dcom.sun.management.jmxremote.port = 9999  \
                 -Dcom.sun.management.jmxremote.authenticate = false \
                 -Dcom.sun.management.jmxremote.ssl = false \
                 jmx.Echo

            認證與授權

            JMX的認證與授權是非常必要的,我們不可能允許任何client都能連接我們的Server。JMX的認證和授權可以復雜的使用LDAP, SSL。也可以使用最簡單的文件存儲用戶信息方式。本文作為啟蒙,只給出最簡單的認證方式。

            在java啟動的時候,添加運行參數:

            java -Dcom.sun.management.jmxremote.port = 9999  \
                 -Dcom.sun.management.jmxremote.authenticate = true \
                 -Dcom.sun.management.jmxremote.password.file = pathTo/my.password \
                 -Dcom.sun.management.jmxremote.access.file = pathTo/my.access \
                 -Dcom.sun.management.jmxremote.ssl = false \
                 jmx.Echo

            my.password里面定義了用戶名和密碼:

            user1 password1
            user2 password2

            my.access里面定義了用戶授權信息:

            user1 readOnly
            user2 readWrite \
                  create jmx.*,javax.management.timer.* \
                  unregister

            更詳細的內容可以從這里找到: http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html 。

            現在可以啟動程序了。啟動以后,我們使用下面的Client來連接我們寫的JMX Agent.

            JMX Client

            JConsole

            JDK提供了一個工具在jdk/bin目錄下面,這就是JConsole。使用JConsole可以遠程或本地連接JMX agent。如下圖所以:

            無論是遠程還是本地,連接進去所看到的都一樣。進去MBeans面板以后,找到MessageEngine。MessageEngine下面有Attributes, Operations和Notification。可以瀏覽MessageEngine中的Attributes并更改那些可寫的屬性。也可以執行Operations下面的stop, pause方法。此外,必須訂閱Notifications才能收到消息。

            JConsole有缺點,它只能對MXBean中的主要基本類型做修改,但不能修改復雜類型。

            Custom Client

            我們也可以用java寫client調用Agent。

            public class Client implements NotificationListener {

                public static void main(String[] args) {         try {             new Client().start();         } catch (Exception e) {             e.printStackTrace();         }     }          public void start() throws Exception {         // 如果agent不做配置的話,默認jndi path為jmxrmi         JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9999/jmxrmi");         JMXConnector jmxc = JMXConnectorFactory.connect(url, null);         MBeanServerConnection server = jmxc.getMBeanServerConnection();         ObjectName mbeanName = new ObjectName("com.example:type=MessageEngine");         // 訂閱Notification         server.addNotificationListener(mbeanName, this, null, null);                  // 訪問paused屬性。         boolean paused = (Boolean)server.getAttribute(mbeanName, "Paused");         System.out.println(paused);         if (!paused) {             server.invoke(mbeanName, "pause", new Object[]{true}, new String[]{"boolean"});         }         // 構建一個jmx.Message類型實例。         CompositeType msgType = new CompositeType("jmx.Message", "Message Class Name",                   new String[]{"title","body", "by"},                   new String[]{"title","body", "by"},                    new OpenType[]{SimpleType.STRING,SimpleType.STRING,SimpleType.STRING});         CompositeData msgData = new CompositeDataSupport(msgType,                 new String[]{"title","body","by"},                 new Object[]{"Hello", "This is a new message.", "xpbug"});          // 調用changeMessage方法         server.invoke(mbeanName, "changeMessage", new Object[]{msgData}, new String[]{CompositeData.class.getName()});         server.invoke(mbeanName, "pause", new Object[]{false}, new String[]{"boolean"});                  // 訪問修改后的Message屬性。         msgData = (CompositeData)server.getAttribute(mbeanName, "Message");         System.out.println("The message changes to:");         System.out.println(msgData.get("title"));         System.out.println(msgData.get("body"));         System.out.println(msgData.get("by"));                  Thread.sleep(1000*10);     }

                @Override     public void handleNotification(Notification notification, Object handback) {         System.out.println(" Handling new notification ");         System.out.println("Message: " + notification.getMessage());         System.out.println("Seq: " + notification.getSequenceNumber());         System.out.println("*");             } }</pre>

            運行一下client,看看都發生了什么。

            源碼下載

            http://pan.baidu.com/s/1sjLKewX 

            來自:http://my.oschina.net/xpbug/blog/221547

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!