使用Metrics方法级远程监控Java程序

栏目: Java · 发布时间: 8年前

内容简介:使用Metrics方法级远程监控Java程序

本文以Spring Web的后台开发讲解。

上一篇讲解了如何使用 jvisualvm 监控 Java 程序。虽然已经挺强大了,但是在实际的应用中依然不满足我们的需求。现在,我们想要监控应用程序中所有 Controller 提供的接口的访问数量,频次,响应时长。 Service 层方法的执行次数,执行时长,频次等等。以便之后对系统的性能优化做准备。这个时候 jvisualvm 已经不能满足我们的需求了。

1 方法级监控Java程序的方案

这是我对于监控Java程序中的方案:

  1. 付费的,比如YourKit,JProfile等。我尝试了YourKit,功能确实强大,但是现在性能并不是我们现在的瓶颈,我们尽量使用不付费的。
  2. Metrics-Spring。Metrics-Spring需要在每个方法上使用注解。我们采用微服务架构,20多个服务,每个工程预计平均有100左右个方法要监控。如果是一开始就用这个我觉得还可以。
  3. Metrics+Spring AOP。从Metrics-Spring中可以看到,Metrics统计的信息基本满足我们的需求。我们的项目需求是统计 Controller 层和 Service 层的方法。那么可以通过Spring中的切面完成我们的需求。

我调查的方案和分析基本这样,其他人如果有更好的方案可以提出一起探讨。

下面是讲解+部分代码,本次讲解还有后篇。在第二篇文后,

2 Metrics的功能

关于Metrics的使用方法,已经有很多文章介绍了,我在这里推荐我认为还不错的给大家,然后我再介绍的使用方法.

  1. Metrics介绍。这篇文章对Metrics的基本功能介绍的已经很全面了。
  2. Metrics-Spring官方文档 。这篇文章介绍了Metrics与Spring的集成,但是文档感觉不全呀。

其他的文章我就不多分享了,感觉大同小异。没什么太大差别。

3 将Metrics相关类装载到Spring容器

要使用Metric,那么首先需要 MetricRegistry 。我们需要提供Http的报表,所以我们需要将 MetricsServlet 注册到Spring中,以便可以通过Http接口监控系统。下面代码我们将Http接口定义为: /monitor/metrics

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.servlets.MetricsServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MonitorConfig {

    @Bean
    public MetricRegistry metricRegistry() {
        return new MetricRegistry();
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean(MetricRegistry metricRegistry) {
        return new ServletRegistrationBean(new MetricsServlet(metricRegistry), "/monitor/metrics");
    }

}

4 提供可控的终端报表

另外,为了方便调试,我希望支持终端报表的方式,并且要可以配置打开和关闭,于是我使用另外一个配置类:

import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import lombok.extern.java.Log;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.concurrent.TimeUnit;

@Configuration
@Log
@ConditionalOnProperty(prefix = "monitor.report", name = "console", havingValue = "true")
@Import(MonitorConfig.class)
public class MonitorReportConfig {


    @Bean
    public ConsoleReporter consoleReporter(MetricRegistry metrics) {
        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .build();
        reporter.start(10, TimeUnit.SECONDS);
        return reporter;
    }

}

这样可以在工程中的 application.properties 文件中,通过下面配置开启终端报表,而且自动开启,10秒钟一次报表:

monitor.report.console = true

5 为要监控的方法准备Timer

Metrics中可以统计的信息很多,其中Timer已经满足了我们需要的信息。

我为什么要先为监控的方法准备Timer,而不是在方法执行的时候再创建呢?原因有两点。

  1. 我们既关心方法被调,也关心它从来没有被调用,如果是在方法执行的时候再创建,那么我们就不知道是方法没有被监控还是方法没有被调用了。
  2. 我们之后打算直接对@RestController,@Controller和@Service注解进行切面。这种类级别的切面力度会包含我们不关心的方法,例如toString等方法,所以准备好关心的方法,调用的时候发现不是我们关心的方法直接放过。

我们使用 MethodMonitorCenter 类来收集我们想要监控的方法。通过实现 ApplicationContextAware 接口,在Spring容器装载完毕之后,会回掉 setApplicationContext 方法,我们通过 getBeansWithAnnotation 方法找到包含指定注解的类。然后对其进行过滤,并获取我们想要监控的方法。在最后我们通过 metricRegistry.timer(method.toString()); 方法为我们的关心的方法准备一个timer。

@Component
@Getter
@Log
public class MethodMonitorCenter implements ApplicationContextAware {

    public static final String PACKAGE_NAME = "com.sinafenqi";  // 这里换成自己的包名

    @Autowired
    private MetricRegistry metricRegistry;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> monitorBeans = new HashMap<>();
        monitorBeans.putAll(applicationContext.getBeansWithAnnotation(Controller.class));
        monitorBeans.putAll(applicationContext.getBeansWithAnnotation(Service.class));
        monitorBeans.putAll(applicationContext.getBeansWithAnnotation(RestController.class));

        log.info("monitor begin scan methods");
        monitorBeans.values().stream()
                .map(obj -> obj.getClass().getName())
                .map(this::trimString)
                .map(clzName -> {
                    try {
                        return Class.forName(clzName);
                    } catch (Exception e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .filter(aClass -> aClass.getName().startsWith(PACKAGE_NAME))
                .forEach(this::getClzMethods);
    }

    private void getClzMethods(Class<?> clz) {
        Stream.of(clz.getDeclaredMethods())
                .filter(method -> method.getName().indexOf('$') < 0)
                .forEach(method -> {
                    log.info("add method timer, method name :" + method.toGenericString());
                    metricRegistry.timer(method.toString());
                });
    }

    private String trimString(String clzName) {
        if (clzName.indexOf('$') < 0) return clzName;
        return clzName.substring(0, clzName.indexOf('$'));
    }

}

6 在切面中对方法进行监控

然后我们可以在切面中监控我们关心的方法。这里使用环绕式切面对 RestControllerController ,和 Service 三个注解做切面。这样就可以在方法之前和之后加一些监控代码。当进入around函数的时候,我们先去MetricRegistry中查找有没有对应的timer,如果没有说明是我们不关心的方法,那么我么就可以直接执行,如果存在,那么我就对其进行监控。详情可见代码:

@Component
@Aspect
@Log
public class MetricsMonitorAOP {

    @Autowired
    private MetricRegistry metricRegistry;

    @Pointcut("@within(org.springframework.stereotype.Controller)" +
            "||@within(org.springframework.stereotype.Service)" +
            "||@within(org.springframework.web.bind.annotation.RestController)")
    public void monitor() {

    }

    @Around("monitor()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String target = joinPoint.getSignature().toLongString();
        Object[] args = joinPoint.getArgs();
        if (!metricRegistry.getNames().contains(target)) {
            return joinPoint.proceed(args);
        }
        Timer timer = metricRegistry.timer(target);
        Timer.Context context = timer.time();
        try {
            return joinPoint.proceed(args);
        } finally {
            context.stop();
        }
    }
}

7 效果

之后访问 /monitor/metrics 接口,就可以以Json的数据格式获取监控结果。大家实验的时候记得把 MethodMonitorCenter 类中的 PACKAGE_NAME 常量换成自己的。

现在基本已经实现监控所有Controller,和Service层我们定义的方法了,但是代码依然有很大的优化空间。这些代码是我从Git的版本库中找出来的,自己没有再去尝试,如有问题欢迎留言。请谅解。目前我已经对代码进行了多处优化,优化内容将在下一篇讲解,并会附上源码。

最后欢迎关注我的个人公众号。提问,唠嗑,都可以。

使用Metrics方法级远程监控Java程序

以上所述就是小编给大家介绍的《使用Metrics方法级远程监控Java程序》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Caching

Web Caching

Duane Wessels / O'Reilly Media, Inc. / 2001-6 / 39.95美元

On the World Wide Web, speed and efficiency are vital. Users have little patience for slow web pages, while network administrators want to make the most of their available bandwidth. A properly design......一起来看看 《Web Caching》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具