关键词搜索

源码搜索 ×
×

JMX 使用指南一 Java Management Extensions

发布2022-06-06浏览527次

详情内容

1. 什么是 JMX

JMX,全称 Java Management Extensions,是在 J2SE 5.0 版本中引入的一个功能。提供了一种在运行时动态管理资源的框架,主要用于企业应用程序中实现可配置或动态获取应用程序的状态。JMX 提供了一种简单、标准的监控和管理资源的方式,对于如何定义一个资源给出了明确的模式。

2. JMX 架构

JMX 架构分为三层:

  • 资源层:该层包含 MBean 及其可管理的资源,提供了实现 JMX 技术可管理资源的规范。
  • 代理层:或者称为 MBean Server 层,是 JMX 的核心。充当 MBean 和应用程序之间的中介。
  • 远程管理层:能够让应用程序远程通过 Connector 和 Adapter 访问 MBean Server。

在这里插入图片描述

要通过 JMX 管理资源,我们需要创建 MBean 来表示要管理的资源,然后将其注册到 MBean Server 中。MBean Server 作为所有已注册 MBean 的管理代理,实现对外提供服务以及对内管理 MBean 资源。JMX 不仅仅用于本地管理,JMX Remote API 为 JMX 添加了远程功能,使之可以通过网络远程监视和管理应用程序。我们可以使用 JMX Connector 连接到 MBean Server 并管理注册的资源。例如,可以使用 JDK 自带的 JConsole 连接到本地或远程 MBean Server。

2.1 资源探测层

资源探测层的核心是用于资源管理的 Managed bean,简称 MBean。MBean 表示在 Java 虚拟机中运行的资源,例如应用程序或 Java EE 技术服务(事务监视器、JDBC 驱动程序等)。MBean 可用来收集重点关注的统计信息,比如性能、资源使用以及问题等,也可以用于获取和设置应用程序配置或属性(推/拉模式),也还可以用于故障通知或者状态变更(推送)等。

MBean 有两种基本类型:

  • Standard MBean:这是最简单的一种 MBean。实现了一个业务接口,其中包含属性的 setter 和 getter 以及操作(即方法)。
  • Dynamic MBean:实现 javax.management.DynamicMBean 接口的 MBean,该接口提供了一种列出属性和操作以及获取和设置属性值的方法。

此外还有 Open MBeans、Model MBeans 和 Monitor MBeans。Open MBean 是依赖于基本数据类型的动态 MBean。Model MBean 是可以在运行时配置的动态 MBean。

MBean 是一个轻量级的 Java 类,它知道如何使用、获取和操作其资源,以便为代理和用户提供访问或功能。除了 JVM 会把自身的各种资源以 MBean 注册到 JMX 中,我们自己的配置、监控等资源也可以作为 MBean 注册到 JMX,这样管理程序就可以直接控制我们暴露的 MBean。为此我们需要首先创建一个接口(定义属性和操作),并且接口的名称必须以 MBean 结尾:

public interface CounterMBean {
    // 管理属性
    public int getCounter();
    public void setCounter(int counter);
    // 管理操作
    public void increase();
    public void decrease();
}

    下一步是提供 MBean 接口的实现。JMX 命名约定实现类名为接口名去掉 MBean 后缀。所以我的实现类将是 Counter:

    public class Counter implements CounterMBean {
        private int counter = 0;
        @Override
        public int getCounter() {
            return counter;
        }
        @Override
        public void setCounter(int counter) {
            this.counter = counter;
        }
        // 加1
        @Override
        public void increase() {
            this.counter += 1;
        }
        // 减1
        @Override
        public void decrease() {
            this.counter -= 1;
        }
    }
    
      9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    MBean 允许通过使用 JMX 代理来管理资源。每个 MBean 都会暴露了底层资源的一部分属性和操作:

    • 可以读写的属性,实现包含属性的 setter 和 getter 方法,在这为 counter。
    • 可以调用的方法,可以向它提供参数或者获取返回值,在这有 increase 和 decrease。

    2.2 代理层

    代理层充当管理资源和应用程序之间的中介。代理层提供对来自管理应用程序的管理资源的访问。JMX 代理可以在嵌入在管理资源的机器中的 JVM 中运行,也可以位于远程位置。代理不需要知道它公开的资源以及使用公开 MBean 的管理器应用程序。它充当处理 MBean 的服务,并允许通过通过 Connector 或 Adaptor 公开的协议来操作 MBean。

    代理层的职责之一是将应用程序与管理资源分离。应用程序不会直接引用管理的资源,而是通过 JMX 代理的对象名称引用调用管理操作。代理层的核心组件是 MBean Server,作为 MBean 的注册中心,并允许应用程序发现已注册 MBean 的管理接口。除此之外,代理层提供了四种代理服务,使管理 MBean 更加容易:计时器服务、监控服务、关系服务以及动态 MBean 加载服务。

    想要 MBean Server 可以管理 MBean 资源,首先要把资源注册到 MBean Server 上,任何符合 JMX 的 MBean 资源都可以进行注册。现在我们需要将上面创建的 MBean 实现类 Counter 注册到 MBean Server 中:

    public class CounterManagement {
        public static void main(String[] args) throws Exception {
            // 获取 MBean Server
            MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
            // 创建 MBean
            Counter counter = new Counter();
            counter.setCounter(0);
            // 注册
            ObjectName objectName = new ObjectName("com.common.example.jmx:type=Counter, name=CounterMBean");
            platformMBeanServer.registerMBean(counter, objectName);
            // 防止退出
            while (true) {
                Thread.sleep(3000);
                System.out.println("[INFO] 休眠 3s ..............");
            }
        }
    }
    
      9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    首先我们通过 ManagementFactory 来获取 MBean Server 来注册 MBean。我们会使用 ObjectName 向 MbeanServer 注册 MBean 接口实现类 Counter 实例。ObjectName 由 domain:key 格式构成:

    • domain:可以是任意字符串,但根据 MBean 命名约定,一般使用 Java 包名(避免命名冲突)
    • key:以逗号分隔的’key=value’键值对列表

    我们一般会定义两个 key:

    • type=MXBean 接口的实现类的类名
    • name=自定义的名字

    在这里,我们使用的是:‘com.common.example.jmx:type=Counter, name=CounterMBean’。

    2.3 远程管理层

    远程管理层是 JMX 架构的最外层,该层负责使 JMX 代理对外部世界可用。代理层并没有实现远程访问方法,所以在远程管理层会提供一个远程通信接口对外提供服务。提供对外服务需要通过使用一个或多个 JMX Connector 或者 Adaptor 来实现。Connector 和 Adaptor 允许应用程序通过特定协议访问远程 JMX 代理,这样来自不同 JVM 的应用程序就可以调用 MBean Server 上的管理操作、获取或设置管理属性、实例化和注册新的 MBean,以及注册和接收来自管理资源的通知。

    Connector 是将代理 API 暴露给其他分布式技术,例如 Java RMI,而 Adaptor 则是通过 HTTP 或者 SNMP 等不同的协议提供对 MBean 的可见性。事实上,一个代理可以使用许多不同的技术。Connector 和 Adaptor 在 JMX 环境中提供相同的功能。

    JavaSE 提供了一个 Jconsole 程序,用于通过 RMI 连接到 MBean Server,这样就可以管理整个 Java 进程。下面示例我们会使用 JConsole 来演示效果。

    3. 实战

    我们以实际问题为例,假设我们希望给应用程序添加一个用户黑名单功能,凡是在黑名单中的用户禁止访问,传统的做法是定义一个配置文件,启动的时候读取:

    # blacklist.txt
    a
    b
    ...
    
    • 1
    • 2
    • 3
    • 4

    如果要修改黑名单怎么办?修改配置文件,然后重启应用程序。但是每次都重启应用程序实在是太麻烦了,能不能不重启应用程序?可以自己写一个定时读取配置文件的功能,检测到文件改动时自动重新读取。上述需求本质上就是在应用程序运行期间对参数、配置等进行热更新并要求尽快生效。

    这个需求我们可以尝试使用 JMX 的方式实现,我们不必自己编写自动重新读取的任何代码,只需要提供一个符合 JMX 标准的 MBean 来存储用户黑名单即可。

    3.1 黑名单管理接口 BlacklistMBean

    JMX 的 MBean 通常以 MBean 结尾,因此我们遵循标准命名规范,首先编写一个 BlacklistMBean 接口实现对用户黑名单的管理:

    public interface BlackListMBean {
        // 获取黑名单列表
        public String[] getBlackList();
        // 在黑名单列表中添加一个用户
        public void addBlackItem(String uid);
        // 判断某个用户是否在黑名单中
        public boolean contains(String uid);
        // 获取黑名单大小
        public int getBlackListSize();
    }
    
      9
    • 10

    3.2 黑名单管理实现 BlackList

    MBean 有一个规则,标准 MBean 接口名称必需是在要实现类名后面加上 MBean 后缀。所以我们的黑名单管理实现类必须为 BlackList,其中定义了一个 uidSet 集合存储用户黑名单:

    public class BlackList implements BlackListMBean {
        private Set<String> uidSet = new HashSet<>();
        @Override
        public String[] getBlackList() {
            return uidSet.toArray(new String[0]);
        }
        @Override
        public void addBlackItem(String uid) {
            uidSet.add(uid);
        }
        @Override
        public boolean contains(String uid) {
            return uidSet.contains(uid);
        }
        @Override
        public int getBlackListSize() {
            return uidSet.size();
        }
    }
    
      9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.3 实时热更新黑名单 MBeanServer

    下一步,我们要使用 JMX 来实时热更新这个 MBean,首先我们要把 MBean 注册到 MBeanServer 中,初始黑名单只有两个用户 a 和 b:

    // 获取 MBean Server
    MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
    
    // 创建 MBean 初始黑名单用户为 a 和 b
    BlackList blackList = new BlackList();
    blackList.addBlackItem("a");
    blackList.addBlackItem("b");
    
    // 注册
    ObjectName objectName = new ObjectName("com.common.example.jmx:type=BlackList, name=BlackListMBean");
    platformMBeanServer.registerMBean(blackList, objectName);
    
      9
    • 10
    • 11

    下面我们使用 Socket 接收字符串模拟用户登录,并根据用户黑名单对用户进行拦截:

    String hostname = "localhost";
    int port = 9000;
    // 循环接收
    while (true) {
        // 简单从 Socket 接收字符串模拟接收到的用户Id
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(hostname, port), 0);
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                char[] buffer = new char[8012];
                int bytes;
                while ((bytes = reader.read(buffer)) != -1) {
                    String result = new String(buffer, 0, bytes);
                    String uid = result;
                    // 去掉换行符
                    if (result.endsWith("\n")) {
                        uid = result.substring(0, result.length() - 1);
                    }
                    if (blackList.contains(uid)) {
                        System.out.println("[INFO] uid " + uid + " is in black list");
                    } else {
                        System.out.println("[INFO] uid " + uid + " is not in black list");
                    }
                }
            }
        }
        Thread.sleep(3000);
        System.out.println("[INFO] 休眠 3s ..............");
    }
    
      9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.4 演示

    下一步就是正常启动用户登录应用程序,打开另一个命令行窗口,输入 jconsole 命令启动 JavaSE 自带的一个 JMX 客户端程序:

    在这里插入图片描述

    通过 jconsole 连接到我们当前正在运行的应用程序,在 jconsole 中可直接看到内存、CPU 等资源的监控。点击 MBean Tab,左侧按分类列出所有 MBean,可以在 com.common.example.jmx 下查看我们创建的 MBean 信息:

    在这里插入图片描述

    点击 BlackList 属性,可以看到目前黑名单中用户有 a 和 b 两个用户,即默认的黑名单用户:

    在这里插入图片描述

    我们在 Socket 中输入用户 a、b、c 模拟用户登录,输出日志如下:

    [INFO] uid a is in black list
    [INFO] uid b is in black list
    [INFO] uid c is not in black list
    
    • 1
    • 2
    • 3

    可见,用户 a 和 b 确实被添加到了用户黑名单中了,而用户 c 不在用户黑名单中。我们点击操作 contains,填入用户 c 并点击 contains 按钮,验证用户 c 是否是在黑名单中,如下图所示用户 c 确实不在黑名单中:

    在这里插入图片描述

    现在我们希望用户 c 也添加在黑名单中,点击操作 addBlackItem,填入用户 c 并点击 addBlackItem 按钮。相当于 jconsole 通过 JMX 接口调用了我们自己的 BlacklistMBean 的 addBlackItem() 方法,传入的参数就是填入的用户 c:

    在这里插入图片描述

    再次查看属性 blackList,可以看到结果已经更新了。我们在 Socket 中输入用户 c 模拟用户登录,测试一下黑名单功能是否已生效:

    [INFO] uid c is in black list
    
    • 1

    可见,用户 c 确实被添加到了黑名单中了。现在我们希望从用户黑名单中移除用户 a,点击操作 removeBlackItem 进行移除:

    在这里插入图片描述

    我们在 Socket 中输入用户 a 模拟用户登录测试一下黑名单功能是否生效:

    [INFO] uid a is not in black list
    
    • 1

    可见,用户 a 确实从用户黑名单中移除了。

    相关技术文章

    点击QQ咨询
    开通会员
    返回顶部
    ×
    微信扫码支付
    微信扫码支付
    确定支付下载
    请使用微信描二维码支付
    ×

    提示信息

    ×

    选择支付方式

    • 微信支付
    • 支付宝付款
    确定支付下载