如何远程访问Spring-boot JMX

39

我知道 Spring 自动暴露 JMX beans。我能够使用 VisualVM 在本地访问它。

然而,在生产环境中,我如何使用它的 JMX beans 远程连接到应用程序呢?是否有默认端口或者我需要额外定义一些内容?

谢谢, ray。

4个回答

58
默认情况下,JMX在本地自动可访问,因此在本地运行jconsole将检测到所有本地的Java应用程序,而无需暴露端口。
要通过JMX远程访问应用程序,您必须指定一个RMI注册表端口。需要知道的是,在连接时,JMX会在该端口上初始化,然后在一个随机高端口上建立数据连接,如果中间有防火墙,这将是一个巨大的问题(“嘿,系统管理员,请打开一切,好吗?”)。
为了强制JMX在与您建立的端口相同的端口上进行连接,您有以下几个选项。注意:您可以为JMX和RMI使用不同的端口,也可以使用相同的端口。
选项1:命令行
-Dcom.sun.management.jmxremote.port=$JMX_REGISTRY_PORT 
-Dcom.sun.management.jmxremote.rmi.port=$RMI_SERVER_PORT

如果您正在使用Spring Boot,您可以将此内容放入与您的应用程序部署的(appname).jar文件相邻的(appname).conf文件中。
选项2:Tomcat/Tomee配置
配置一个JmxRemoteLifecycleListener
Maven Jar:
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-catalina-jmx-remote</artifactId>
        <version>8.5.9</version>
        <type>jar</type>
    </dependency>

配置您的 server.xml 文件:
<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
      rmiRegistryPortPlatform="10001" rmiServerPortPlatform="10002" />

选项3:以编程方式进行配置
@Configuration
public class ConfigureRMI {

    @Value("${jmx.rmi.host:localhost}")
    private String rmiHost;

    @Value("${jmx.rmi.port:1099}")
    private Integer rmiPort;

    @Bean
    public RmiRegistryFactoryBean rmiRegistry() {
        final RmiRegistryFactoryBean rmiRegistryFactoryBean = new RmiRegistryFactoryBean();
        rmiRegistryFactoryBean.setPort(rmiPort);
        rmiRegistryFactoryBean.setAlwaysCreate(true);
        return rmiRegistryFactoryBean;
    }

    @Bean
    @DependsOn("rmiRegistry")
    public ConnectorServerFactoryBean connectorServerFactoryBean() throws Exception {
        final ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean();
        connectorServerFactoryBean.setObjectName("connector:name=rmi");
        connectorServerFactoryBean.setServiceUrl(String.format("service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi", rmiHost, rmiPort, rmiHost, rmiPort));
        return connectorServerFactoryBean;
    }
}

这个技巧,你会看到,就是在serviceUrl中指定jmx:rmi主机/端口和jndi:rmi主机/端口。如果你同时指定了两者,就不会出现随机高的“问题”。 编辑:为了使JMX远程工作,你需要做一个关于身份验证的决定。最好分为3个明确的步骤来完成:
  1. 基本设置,使用-Dcom.sun.management.jmxremote.authenticate=false,然后
  2. 添加一个密码文件(-Dcom.sun.management.jmxremote.password.file)。请参阅此处的说明。 + -Dcom.sun.management.jmxremote.ssl=false,然后
  3. 设置SSL。

1
这种方法是否排除了使用下面的 JAVA_OPTS 方法? - raffian
我尝试在 Docker 容器中运行的 Java 服务上使用 Option 3 方法,但是在容器外部连接时遇到了问题。我需要做一个小的示例项目来更好地探索这个想法。 - djangofan
1
@OleksandrSh,文档在这里:https://docs.spring.io/spring-boot/docs/2.3.2.RELEASE/reference/html/deployment.html#deployment-script-customization-conf-file - Dima Korobskiy
@DKroot 相对于 Java 7+,Java 11 在这方面没有什么不同。身份验证标志默认为 true,并且您应该配置一个 jmxremote.password 文件。将标志设置为 false 是一种安全漏洞,除非您在非生产机器上使用,否则不要这样做。 - inanutshellus
1
@inanutshellus 完成了。哇哦:你可以编辑别人的答案!这是我在SO上第一次这样做。我的答案:当然可以。别人的答案:让我大吃一惊。 :) - Dima Korobskiy
显示剩余7条评论

40

在您的应用程序中的"$JAVA_OPTS"中添加以下JVM属性:

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=<PORT_NUMBER> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=<HOST'S_IP>

在Jconsole/Visual VM中使用以下内容进行连接:

service:jmx:rmi:///jndi/rmi://<HOST'S_IP>:<PORT_NUMBER>/jmxrmi

它没有启用安全功能,但可以帮助您连接到远程服务器。


这对我很有帮助...谢谢。直到我添加了-Djava.rmi.server.hostname,它才起作用。我能够通过jconsole连接,像这样:ip:port - em_bo
1
这些参数必须通过java命令行使用。在Spring Boot 2.3.2中,在JAVA_OPTS中设置它们对我没有起作用。这里有一篇更长的博客文章:https://medium.com/@cl4r1ty/docker-spring-boot-and-java-opts-ba381c818fa2。(Docker无关紧要,`JAVA_OPTS="..." java -jar`也不起作用。) - Dima Korobskiy

6

在Java 1.8.0_71和Spring Boot(1.3.3.RELEASE)上测试过的方法。将以下参数添加到监控JVM的JVM参数中。

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=12348 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.rmi.port=12349 -Dcom.sun.management.jmxremote.password.file=/somewhere/jmxremote.password -Dcom.sun.management.jmxremote.access.file=/somewhere/jmx/jmxremote.access
com.sun.management.jmxremote.port用于定义固定的RMI注册表端口,com.sun.management.jmxremote.rmi.port则用于指示JVM使用固定的RMI端口而非随机端口。通过设置这些参数,我可以通过仅打开12348和12349端口的防火墙连接来自远程主机的JVM客户端监视受监视的JVM。我在远程计算机上使用java -jar cmdline-jmxclient-0.10.3.jar user:pwd hostip:12348进行测试,生成以下输出(仅为演示而缩短)。
java.lang:type=Runtime
java.lang:name=PS Scavenge,type=GarbageCollector
Tomcat:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,j2eeType=Filter,name=requestContextFilter
java.nio:name=mapped,type=BufferPool
Tomcat:host=localhost,type=Host
java.lang:name=Compressed Class Space,type=MemoryPool
.......

这个jar包可以从这里下载。


我在OpenJDK 11中成功测试了这种方法,并进行了两项改进:1.使用相同的端口用于JMX和RMI。2.省略“-Dcom.sun.management.jmxremote”:这不再需要。 - Dima Korobskiy

0

另一种选择

jmxremote.password和jmxremote.access文件的参考

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jmx.support.ConnectorServerFactoryBean;
import org.springframework.remoting.rmi.RmiRegistryFactoryBean;

@Configuration
public class ConfigureRMI {

    @Value("${jmx.rmi.password.file:/tmp/jmxremote.password}")
    private String passwordFile;

    @Value("${jmx.rmi.access.file:/tmp/jmxremote.access}")
    private String accessFile;

    @Value("${jmx.rmi.port:19999}")
    private Integer rmiPort;

    @Bean
    public RmiRegistryFactoryBean rmiRegistry() {
        final RmiRegistryFactoryBean rmiRegistryFactoryBean = new RmiRegistryFactoryBean();
        rmiRegistryFactoryBean.setPort(rmiPort);
        rmiRegistryFactoryBean.setAlwaysCreate(true);
        return rmiRegistryFactoryBean;
    }

    @Bean
    @DependsOn("rmiRegistry")
    public ConnectorServerFactoryBean connectorServerFactoryBean() throws Exception {
        final ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean();
        connectorServerFactoryBean.setObjectName("connector:name=rmi");
        Map<String, Object> properties = new HashMap<>();
        properties.put("jmx.remote.x.password.file", passwordFile);
        properties.put("jmx.remote.x.access.file", accessFile);
        connectorServerFactoryBean.setEnvironmentMap(properties);
        connectorServerFactoryBean.setServiceUrl(String.format("service:jmx:rmi:///jndi/rmi://:%s/jmxrmi", rmiPort));
        return connectorServerFactoryBean;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接