跳到主要内容
JackSparrow414
返回

JMX的快速入门与使用+使用JMX Exporter监控+集成OpenTelemetry

本文导航

展开本文导航

JMX应用场景

用来监控JVM内各种对象的信息。一个经典场景就是,某一天我们忽然被前方告知,业务大面积瘫痪,这时经过排查,发现由于bug导致数据库连接使用完了没有被释放,导致后续业务没有可用的数据库连接而超时报错。

假如我们使用JMX来监控我们系统中数据库连接池的信息,当数据库连接池出现短时间内连接被大量使用,这个时候可以搭配我们的监控报警系统(如Nagios或jmx_exporter+Prometheus+Grafana)在问题未出现之前就进行响应,可以极大避免上述情况的发生

MXBean 和 MBean的区别

JMX中分为MBean和MXBean,这里只给出MXBean的例子,如有兴趣,参考下面官方文档获取有关MBean的内容

二者的区别

一句话简单概括二者的区别:MXBean中可以包含对象,而MBean中只能包含基本类型和String。在jconsole中MXBean中的对象可以正常显示,而MBean中的对象显示不出来

更多有关MBean的实践,可以参考各种开源框架里对JMX的支持。如Log4j2中的LoggerContextAdminMBean、ContextSelectorAdminMBean

JMX使用

使用JMX很简单,基本分为以下几步

定义一个MXBean

@MXBean
public interface MyMXBean{

    User getUser();

    String getName();

    void setName(String name);

    void logMessage();

}

这里有两种写法

其他注意事项

实现该MXBean

public class MyMXBeanImpl implements MyMXBean {

    private String name = "jack";

    private User user = User.builder().name("jack").password(888888L).used(false).build();;

    @Override
    public User getUser() {
        return user;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    /**
     * JMX 操作示例
     */
    @Override
    public void logMessage() {
        log.info("log message");
    }
}

将MXBean注册到JMX中去

@Component
public class JMXRegister {

    @PostConstruct
    @SneakyThrows
    public void registerMXBeans() {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        // ObjectName的完整写法可参考 javax.management.ObjectName的注释
        ObjectName mxbeanName = new ObjectName("com.demo.jmx.mbean:type=MyMXBeanImpl, name=custom");
        MyMXBeanImpl myMXBean = new MyMXBeanImpl();
        mbs.registerMBean(myMXBean, mxbeanName);
    }
}

可以看到这里很简单,就是new了一个我们需要监控的Bean,然后给它一个名字。其中ObjectName的写法为

xxx:type=MXBean接口的实现类的类名, name=自定义的名字

使用Jconsole监控

启动应用之后,启动Jconsole,启动之后找到我们应用的进程名字,点击连接。连接进去之后在MBean那里即可找到刚才自定义的MXBean 可以看到属性和操作 JConsole MBeans 页显示自定义 MXBean 的属性与操作 在属性值CompositeDataSupport上双击可以看到对象内部的属性,再次双击则隐藏 JConsole 展开 CompositeDataSupport 后显示对象内部属性 JConsole 操作自定义 MXBean 后显示返回结果 调用 MXBean 操作后应用控制台打印的日志 在具体属性下面,可以进行查看和修改具体属性的具体值

注意 : 具体属性的值能不能修改取决去属性是由什么修饰,如果是由final修饰,那肯定是修改不了的

这里可以看到我们的MXBean在JMX中,它的ObjectName到底是什么。这一点很有用,当我们想要监控一个第三方类库中的MXBean,但是不知道它在JMX中叫什么时,可以在本地启动代码,然后在Jconsole中找到它 JConsole MBean 信息页显示 ObjectName 与 Java 类名

如果想要连接远程JMX,则可以使用类似以下这种写法

service:jmx:rmi://192.168.30.10:1234/jndi/rmi://192.168.30.10:2344/jmxrmi
service:jmx:rmi://<TARGET_MACHINE>:<JMX_RMI_SERVER_PORT>/jndi/rmi://<TARGET_MACHINE>:<RMI_REGISTRY_PORT>/jmxrmi

这样一看就清晰多了吧。如果还不理解,可以看Stackoverflow关于JMX的url的解释

查询Mbean

    /**
     * curl 'http://localhost:18081/jmx/mbean?objectName=com.demo.jmx.mbean%3Atype%3DMyMXBeanImpl%2C%20name%3Dcustom'
     * @param objectName
     * @return
     */
    @SneakyThrows
    @GetMapping
    public String queryMbeans(@RequestParam String objectName) {
        MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName mxbeanName = new ObjectName(objectName);
//        如果传null,则查询所有MBean
//        platformMBeanServer.queryMBeans(null, null);
        MBeanAttributeInfo[] mBeanAttributeInfos = platformMBeanServer.getMBeanInfo(mxbeanName).getAttributes();
        for (MBeanAttributeInfo mBeanAttributeInfo : mBeanAttributeInfos) {
            log.info(mBeanAttributeInfo.getName());
            log.info(mBeanAttributeInfo.getClass().getName());
        }
//        通过MBeanAttributeInfo拿AttributeList
        AttributeList attributes = platformMBeanServer.getAttributes(mxbeanName, Arrays.stream(mBeanAttributeInfos).map(MBeanFeatureInfo::getName).toArray(String[]::new));
//        遍历AttributeList获取每个attribute的值
        attributes.forEach(each -> {
            if (each instanceof Attribute) {
                String value = ((Attribute) each).getValue().toString();
                log.info(value);
            }
        });

        return "ok";
    }

定义MBean时要注意名字要符合规范

使用MBean时,接口名字为HelloMBean时,其实现类名字一定要为接口名字去掉MBean后缀即Hello。否则会报类似异常

MBean class com.demo.jmx.mbean.HelloJmx does not implement DynamicMBean, and neither follows the Standard MBean conventions (javax.management.NotCompliantMBeanException: Class com.demo.jmx.mbean.HelloJmx is not a JMX compliant Standard MBean)

官方Standard MBeans文档提到了这个标准

By convention, an MBean interface takes the name of the Java class that implements it, with the suffix MBean added

JMX 监控

使用jmx_exporter集成Prometheus来监控JMX

现有监控JMX的方案有很多,其底层核心方法都是上述查询MBean的方法,只不过做了封装和配置。比较流行的是jmx_exporter,随着OpenTelemetry的发展,我们决定将系统的监控架构逐渐过渡到OpenTelemetry。

基本的架构是

  1. Tomcat 通过jmx_exporter agent查询JMX指标并由其格式化为openmetrics标准
  2. Tomcat 所在的服务器安装OTEL Collector,在Collector中使用prometheus-receiver定时向jmx_exporter发起抓取请求
  3. 将抓取到的指标通过prometheus-exporter暴露到某个端口,例如8899,暴露出来的指标会保持5分钟,参考metric_expiration参数说明
  4. Prometheus 服务器定时去8899端口抓取数据

还有一些不常见的监控jmx的插件,例如Nagios的check_jmx插件

如何处理jmx_exporter的Broken pipe异常 / 提高jmx_exporter抓取性能?

一般出现这种异常都是因为jmx_exporter收集指标过多,导致收集指标时间过长,从而导致连接断开。这时候需要配置includeObjectNames 或 excludeObjectNames ,这两个属性是针对MBean的,pattern 这个属性是针对过滤MBean下的属性的。

实践过程中建议只配置自己需要的MBean和metrics,这里给一个简单的例子

# for the metrics of the jvm itself, we don't have to declare them, they are automatically exported, see https://groups.google.com/g/prometheus-users/c/2WTZn5Vi4FE
includeObjectNames:
  - "org.apache.commons.pool2:type=GenericObjectPool,*"
  - "tomcat.jdbc:*"
  - "Catalina:type=Manager,*"
# using cache parameters to increase performance, note that this parameter only caches bean name expressions to rule computation and not cache metrics. see https://github.com/prometheus/jmx_exporter/tree/release-1.0.1/docs
rules:
  - pattern: 'org.apache.commons.pool2<type=GenericObjectPool, name=(\w+)><>(NumActive)'
    cache: true
  - pattern: 'tomcat.jdbc<name=\"\w+/\w+\", .*><>(NumActive)'
    cache: true
  - pattern: "Catalina<type=Manager,.*><>(activeSessions)"
    cache: true
  1. jmx_exporter会自动导出jvm本身的一些数据(见prometheus client_java文档和邮件列表),例如memory和thread,且这些对象目前不可被过滤(见官方issues)
  2. 实践中最好根据实际情况配置includeObjectNames,只抓取自己需要的MBean以提高性能。还可以为rules配置cache参数,也能提高性能
  3. 过滤也可以在Prometheus或Opentelemetry Collector中配置。这部分不详细展开,感兴趣的同学可自行研究
  4. 监控jmx_scrape_duration_seconds ,看什么时候会抓取数据超时。目前我们遇到的问题是,includeObjectNames本身配置的MBean很少了,但是偶尔还会出现jmx_exporter抓取metrics的时候会长达几十秒甚至一百秒+的情况,目前没找到原因(官方说过jmx本身并不是很高效),有遇到类似情况的同学可以分享下解决方案。 2024-11-15日更新:此问题已解决,解决方案见解决jmx-exporter抓取指标超时问题

集成OpenTelemetry JMX Metric Insight

如果你们现在正在使用或逐渐过渡到opentelemetry,也可以使用opentelemetry的JMX Metric Insight。这里不做详细介绍,有兴趣的可自行研究。官方相关Blog

集成OpenTelemetry JMX Metric Gatherer

类似于jmx-exporter httpserver模式。不过目前该组件的开发状态未知

集成OpenTelemetry JMX Metric Scraper

类似于jmx-exporter agent模式。目前该组件还在开发中。还有一个JMX Receiver组件,不过该组件目前状态是未维护

以上三种方式参考自Customizing_JMX_Metric_Collection_with_OpenTelemetry文章

JMX官方文档

其他框架用到JMX的示例

JMX使用的身影可以在很多地方都可以找到,这里列举两个

源码地址


分享本文:

上一篇
SpringBoot使用Velocity发送电子邮件
下一篇
Docker官方文档阅读笔记

评论

欢迎提问、纠错和分享经验。发表评论需要登录 GitHub;中英文版本共享评论。

评论仅在正式网站开放。