阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

集成JMX

30次阅读
没有评论

共计 5425 个字符,预计需要花费 14 分钟才能阅读完成。

在 Spring 中,可以方便地集成 JMX。

那么第一个问题来了:什么是 JMX?

JMX 是 Java Management Extensions,它是一个 Java 平台的管理和监控接口。为什么要搞 JMX 呢?因为在所有的应用程序中,对运行中的程序进行监控都是非常重要的,Java 应用程序也不例外。我们肯定希望知道 Java 应用程序当前的状态,例如,占用了多少内存,分配了多少内存,当前有多少活动线程,有多少休眠线程等等。如何获取这些信息呢?

为了标准化管理和监控,Java 平台使用 JMX 作为管理和监控的标准接口,任何程序,只要按 JMX 规范访问这个接口,就可以获取所有管理与监控信息。

实际上,常用的运维监控如 Zabbix、Nagios 等工具对 JVM 本身的监控都是通过 JMX 获取的信息。

因为 JMX 是一个标准接口,不但可以用于管理 JVM,还可以管理应用程序自身。下图是 JMX 的架构:

    ┌─────────┐  ┌─────────┐
    │jconsole │  │   Web   │
    └─────────┘  └─────────┘
         │            │
┌ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─
 JVM     ▼            ▼        │
│   ┌─────────┐  ┌─────────┐
  ┌─┤Connector├──┤ Adaptor ├─┐ │
│ │ └─────────┘  └─────────┘ │
  │       MBeanServer        │ │
│ │ ┌──────┐┌──────┐┌──────┐ │
  └─┤MBean1├┤MBean2├┤MBean3├─┘ │
│   └──────┘└──────┘└──────┘
 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

JMX 把所有被管理的资源都称为 MBean(Managed Bean),这些 MBean 全部由 MBeanServer 管理,如果要访问 MBean,可以通过 MBeanServer 对外提供的访问接口,例如通过 RMI 或 HTTP 访问。

注意到使用 JMX 不需要安装任何额外组件,也不需要第三方库,因为 MBeanServer 已经内置在 JavaSE 标准库中了。JavaSE 还提供了一个 jconsole 程序,用于通过 RMI 连接到 MBeanServer,这样就可以管理整个 Java 进程。

除了 JVM 会把自身的各种资源以 MBean 注册到 JMX 中,我们自己的配置、监控信息也可以作为 MBean 注册到 JMX,这样,管理程序就可以直接控制我们暴露的 MBean。因此,应用程序使用 JMX,只需要两步:

  1. 编写 MBean 提供管理接口和监控数据;
  2. 注册 MBean。

在 Spring 应用程序中,使用 JMX 只需要一步:

  1. 编写 MBean 提供管理接口和监控数据。

第二步注册的过程由 Spring 自动完成。我们以实际工程为例,首先在 AppConfig 中加上 @EnableMBeanExport 注解,告诉 Spring 自动注册 MBean:

@Configuration
@ComponentScan
@EnableWebMvc
@EnableMBeanExport // 自动注册 MBean
@EnableTransactionManagement
@PropertySource({"classpath:/jdbc.properties"})
public class AppConfig {...}

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

# blacklist.txt
1.2.3.4
5.6.7.8
2.2.3.4
...

如果要修改黑名单怎么办?修改配置文件,然后重启应用程序。

但是每次都重启应用程序实在是太麻烦了,能不能不重启应用程序?可以自己写一个定时读取配置文件的功能,检测到文件改动时自动重新读取。

上述需求本质上是在应用程序运行期间对参数、配置等进行热更新并要求尽快生效。如果以 JMX 的方式实现,我们不必自己编写自动重新读取等任何代码,只需要提供一个符合 JMX 标准的 MBean 来存储配置即可。

还是以 IP 黑名单为例,JMX 的 MBean 通常以 MBean 结尾,因此我们遵循标准命名规范,首先编写一个BlacklistMBean

public class BlacklistMBean {private Set<String> ips = new HashSet<>();

    public String[] getBlacklist() {return ips.toArray(String[]::new);
    }

    public void addBlacklist(String ip) {ips.add(ip);
    }

    public void removeBlacklist(String ip) {ips.remove(ip);
    }

    public boolean shouldBlock(String ip) {return ips.contains(ip);
    }
}

这个 MBean 没什么特殊的,它的逻辑和普通 Java 类没有任何区别。

下一步,我们要使用 JMX 的客户端来实时热更新这个 MBean,所以要给它加上一些注解,让 Spring 能根据注解自动把相关方法注册到 MBeanServer 中:

@Component
@ManagedResource(objectName = "sample:name=blacklist", description = "Blacklist of IP addresses")
public class BlacklistMBean {private Set<String> ips = new HashSet<>();

    @ManagedAttribute(description = "Get IP addresses in blacklist")
    public String[] getBlacklist() {return ips.toArray(String[]::new);
    }

    @ManagedOperation
    @ManagedOperationParameter(name = "ip", description = "Target IP address that will be added to blacklist")
    public void addBlacklist(String ip) {ips.add(ip);
    }

    @ManagedOperation
    @ManagedOperationParameter(name = "ip", description = "Target IP address that will be removed from blacklist")
    public void removeBlacklist(String ip) {ips.remove(ip);
    }

    public boolean shouldBlock(String ip) {return ips.contains(ip);
    }
}

观察上述代码,BlacklistMBean首先是一个标准的 Spring 管理的 Bean,其次,添加了 @ManagedResource 表示这是一个 MBean,将要被注册到 JMX。objectName 指定了这个 MBean 的名字,通常以 company:name=Xxx 来分类 MBean。

对于属性,使用 @ManagedAttribute 注解标注。上述 MBean 只有 get 属性,没有 set 属性,说明这是一个只读属性。

对于操作,使用 @ManagedOperation 注解标准。上述 MBean 定义了两个操作:addBlacklist()removeBlacklist(),其他方法如shouldBlock() 不会被暴露给 JMX。

使用 MBean 和普通 Bean 是完全一样的。例如,我们在 BlacklistInterceptor 对 IP 进行黑名单拦截:

@Order(1)
@Component
public class BlacklistInterceptor implements HandlerInterceptor {final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    BlacklistMBean blacklistMBean;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {String ip = request.getRemoteAddr();
        logger.info("check ip address {}...", ip);
        // 是否在黑名单中:
        if (blacklistMBean.shouldBlock(ip)) {logger.warn("will block ip {} for it is in blacklist.", ip);
            // 发送 403 错误响应:
            response.sendError(403);
            return false;
        }
        return true;
    }
}

下一步就是正常启动 Web 应用程序,不要关闭它,我们打开另一个命令行窗口,输入 jconsole 启动 JavaSE 自带的一个 JMX 客户端程序:

集成 JMX

通过 jconsole 连接到一个 Java 进程最简单的方法是直接在 Local Process 中找到正在运行的AppConfig,点击 Connect 即可连接到我们当前正在运行的 Web 应用,在 jconsole 中可直接看到内存、CPU 等资源的监控。

我们点击 MBean,左侧按分类列出所有 MBean,可以在 java.lang 查看内存等信息:

集成 JMX

细心的童鞋可以看到 HikariCP 连接池也是通过 JMX 监控的。

sample 中可以看到我们自己的 MBean,点击可查看属性blacklist

集成 JMX

点击 OperationsaddBlacklist,可以填入127.0.0.1 并点击 addBlacklist 按钮,相当于 jconsole 通过 JMX 接口,调用了我们自己的 BlacklistMBeanaddBlacklist()方法,传入的参数就是填入的127.0.0.1

集成 JMX

再次查看属性blacklist,可以看到结果已经更新了:

集成 JMX

我们可以在浏览器中测试一下黑名单功能是否已生效:

集成 JMX

可见,127.0.0.1确实被添加到了黑名单,后台日志打印如下:

2020-06-06 20:22:12 INFO  c.i.l.web.BlacklistInterceptor - check ip address 127.0.0.1...
2020-06-06 20:22:12 WARN  c.i.l.web.BlacklistInterceptor - will block ip 127.0.0.1 for it is in blacklist.

注意:如果使用 IPv6,那么需要把 0:0:0:0:0:0:0:1 这个本机地址加到黑名单。

如果从 jconsole 中调用 removeBlacklist 移除127.0.0.1,刷新浏览器可以看到又允许访问了。

使用 jconsole 直接通过 Local Process 连接 JVM 有个限制,就是 jconsole 和正在运行的 JVM 必须在同一台机器。如果要远程连接,首先要打开 JMX 端口。我们在启动 AppConfig 时,需要传入以下 JVM 启动参数:

  • -Dcom.sun.management.jmxremote.port=19999
  • -Dcom.sun.management.jmxremote.authenticate=false
  • -Dcom.sun.management.jmxremote.ssl=false

第一个参数表示在 19999 端口监听 JMX 连接,第二个和第三个参数表示无需验证,不使用 SSL 连接,在开发测试阶段比较方便,生产环境必须指定验证方式并启用 SSL。详细参数可参考 Oracle 官方文档。这样 jconsole 可以用 ip:19999 的远程方式连接 JMX。连接后的操作是完全一样的。

许多 JavaEE 服务器如 JBoss 的管理后台都是通过 JMX 提供管理接口,并由 Web 方式访问,对用户更加友好。

在实际项目中,通过 JMX 实现配置的实时更新其实并不常用,JMX 更多地用于收集 JVM 的运行状态和应用程序的性能数据,然后通过监控服务器汇总数据后实现监控与报警。一个典型的监控系统架构如下:

┌───────────────┐   ┌───────────────┐
│  Web Console  │◀──│Metrics Server │
└───────────────┘   └───────────────┘
                            │
                            │
   ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ┐
     ┌───────────────┐      │
   │ │      App      │      │    │
     ├─────────┬─────┤   ┌─────┐
   │ │         │ JMX │──▶│Agent│ │
     │         └─────┤   └─────┘
   │ │      JVM      │           │
     └───────────────┘
   └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

其中,App 自身和 JVM 的的统计数据都通过 JMX 收集并发送给本机的一个 Agent,Agent 再将数据发送至监控服务器,最后以可视化的形式将监控数据通过 Web 等形式展示给用户。常用的监控系统有开源的 Prometheus 和以云服务方式提供的 DataDog 等。

练习

编写一个 MBean 统计当前注册用户数量,并在 jconsole 中查看:

下载练习

小结

在 Spring 中使用 JMX 需要:

  • 通过 @EnableMBeanExport 启用自动注册 MBean;
  • 编写 MBean 并实现管理属性和管理操作。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计5425字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中