Vert.x Dropwizard指标数据监控

全文共 3938 个字

最近项目中需要针对Vert.x的运行效率进行监控,查阅Vert.x官文,发现目前提供了Dropwizard和Hawkular两种开箱即用的工具。本文将介绍使用Dropwizard Metrics实现Vert.x性能统计的过程(当然还有踩过的坑)。

首先简要说说dropwizard metrics。

Dropwizard Metrics

按照官网的说法:Metrics是一个Java库,这个库可以让我们有无可比拟的能力去了解编码是如何在生产环境运行的。Metrics提供了强大的工具来测量关键组件在生产环境的运行行为。

如果仅仅使用Metrics的功能,其实并没有什么复杂,也就是嵌入一些类去使用。把他理解log4j就很容易了。

在运行之前,通过Maven引入依赖关系:


            <dependency>
                <groupId>io.dropwizard.metrics</groupId>
                <artifactId>metrics-core</artifactId>
                <version>${metrics.version}</version>
            </dependency>

metrics.version 使用最新的版本号。

下面的例子简要说明了如何使用metric。例子中先是包装了一个具有监控功能的队列,在调用add和remove方法时更新指标数据。

//声明一个自带指标功能的队列
class QueueWarp {
	// 真实队列
    private final Queue<Object> queue;
    // 计数器指标
    private Counter counter;
    // 柱状图指标
    private Histogram size_rate;
    // 计时器指标
    private Timer optTimer;
    // 构造函数
    public QueueWarp(MetricRegistry metrics, String name) {
        this.queue = new LinkedList<Object>();

        //注册一个测量值对象
        metrics.register(MetricRegistry.name(QueueWarp.class, name, "size"),
                         new Gauge<Integer>() {
                             @Override
                             //每次发起统计时获取数据的接口
                             public Integer getValue() {
                                 return queue.size();
                             }
                         });
        // 注册计数器
        counter = metrics.register(MetricRegistry.name(QueueWarp.class, name, "count"), new Counter());
        // 注册柱状图
        size_rate = metrics.histogram(MetricRegistry.name(QueueWarp.class, "size-rate"));
        // 注册计时器
        optTimer = metrics.timer(MetricRegistry.name(QueueWarp.class, "opt-timer"));
    }
    //增加
    public boolean add(Object e){
    	final Timer.Context timerContext = optTimer.time();//开始计时
    	counter.inc();//计数器+1
    	size_rate.update(queue.size());//柱状图更新
    	boolean ret = this.queue.add(e);//添加数据到队列
    	timerContext.stop();//停止计时
        return ret;
    }
    // 删除
    public Object remove(Object e){
    	final Timer.Context timerContext = optTimer.time();
    	counter.dec();
    	size_rate.update(queue.size());
    	Object ret = this.queue.remove();
    	timerContext.stop();
        return ret;
    }
}

在上面的代码中,当调用add、remove方法时,会记录:

  1. 方法从调用到返回的时间间隔。
  2. 更新队列中的数据规模。(Counter和Gauge都记录了规模)
  3. 更新当前队列成员个数和最大值的比率。

然后使用一个main方法来测试这个类并输出指标数据:

public class MetricDemoRun {
	// 注册指标实例
	static final MetricRegistry metrics = new MetricRegistry();
	public static void main(String args[]) {
		//新建队列
		QueueWarp queue = new QueueWarp(metrics, "jobss");
		queue.add("1");
		// 启动指标数据输出
		startReport();
		try {
			Thread.sleep(5 * 1000);
		} catch (InterruptedException e) {
		}
		queue.add("2");
		Meter requests = metrics.meter("requests");
		requests.mark();
		wait5Seconds();
	}

	static void startReport() {
		//注册报告对象
		ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).
				convertRatesTo(TimeUnit.SECONDS).
				convertDurationsTo(TimeUnit.MILLISECONDS)
				.build();

		// 开始输出报告
		reporter.start(1, TimeUnit.SECONDS);
	}

	static void wait5Seconds() {
		try {
			Thread.sleep(300 * 1000);
		} catch (InterruptedException e) {
		}
	}
}

MetricDemoRun类中还有2个静态方法,一个用来输出报告数据。一个用来将主线程睡眠一段的时间。运行main以后,会在控制台重复输出下列内容:

16-7-7 16:50:35 ================================================================

-- Gauges ----------------------------------------------------------------------
com.oakss.demo.metrics.app.QueueWarp.jobss.size
             value = 2

-- Counters --------------------------------------------------------------------
com.oakss.demo.metrics.app.QueueWarp.jobss.count
             count = 2

-- Histograms ------------------------------------------------------------------
com.oakss.demo.metrics.app.QueueWarp.size-rate
             count = 2
               min = 0
               max = 1
              mean = 0.52
            stddev = 0.50
            median = 1.00
              75% <= 1.00
              95% <= 1.00
              98% <= 1.00
              99% <= 1.00
            99.9% <= 1.00

-- Meters ----------------------------------------------------------------------
requests
             count = 1
         mean rate = 1.00 events/second
     1-minute rate = 0.00 events/second
     5-minute rate = 0.00 events/second
    15-minute rate = 0.00 events/second

-- Timers ----------------------------------------------------------------------
com.oakss.demo.metrics.app.QueueWarp.opt-timer
             count = 2
         mean rate = 0.33 calls/second
     1-minute rate = 0.20 calls/second
     5-minute rate = 0.20 calls/second
    15-minute rate = 0.20 calls/second
               min = 0.07 milliseconds
               max = 2.75 milliseconds
              mean = 1.35 milliseconds
            stddev = 1.34 milliseconds
            median = 0.07 milliseconds
              75% <= 2.75 milliseconds
              95% <= 2.75 milliseconds
              98% <= 2.75 milliseconds
              99% <= 2.75 milliseconds
            99.9% <= 2.75 milliseconds
 

看完上面的例子。并没有感受什么特别牛逼的地方,无非是在代码中嵌入一些统计点。 看来看去都像一个增强版的日志工具。

Vert.x指标统计    

嵌入Metrics

说完基本的dropwizard metrics功能我们再看看如何整合Vert.x和dropwizard metrics用来统计各种有效的指标。

Vert.x通过MetricsService的SPI接口提供了接入指标统计工具的入口。在创建Vertx实例时使用DropwizardMetricsOptions来告诉Vertx使用对应的实现类。首先需要加入Vert.x的Dropwizard包:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-dropwizard-metrics</artifactId>
  <version>3.3.0</version>
</dependency>

然后下列代码展示了创建一个具有Metrics功能的Vertx实例以及从中读取指标数据。

public class VertxMetricDemo {
	public static void main(String[] args) {
		// 使用DropwizardMetricsOptions配置创建单机Vertx实例
		Vertx vertx = Vertx.vertx(
				new VertxOptions().setMetricsOptions(
						new DropwizardMetricsOptions().setEnabled(true)
				));

		// 创建指标服务
		MetricsService metricsService = MetricsService.create(vertx);

		// 获取当前的指标服务
		JsonObject json = metricsService.getMetricsSnapshot(vertx);

		// 输出
		System.out.println(json);
	}
}

Vert.x实现指标功能的原理是接口继承Measured,这些接口包括HttpServer、NetServer、EventBus、Vertx等。通过Measured可以注入对各种组件的指标统计。

MetricsService提供了丰富的功能接口来获取各种指标数据,每一项指标数据都有自己特定的命名规则。我们可以过全称获取某一项指标,例如获取eventBus上的handler相关指标,可以使用以下方法:

JsonObject metrics = metricsService.getMetricsSnapshot(vertx);
metrics.getJsonObject("vertx.eventbus.handlers");

或者直接从指定的eventBus获取数据:

EventBus eventBus = vertx.eventBus();
JsonObject metrics = metricsService.getMetricsSnapshot(eventBus);
metrics.getJsonObject("handlers");

两种方式获得同样的数据,只要接口继承了Measured就可以用MetricsService::create来获取指标。

指标格式和指标数据

Metric提供了丰富的数据格式。目前有:Gauge(测量值)、Counter(计数器)、Histogram(柱状图)、Meter(仪表)、ThroughputMeter(吞吐量统计)、Timer(计时器)和Throughput Timer(吞吐量计时器)。

Vert.x提供了丰富的指标数据内容,下面将一一列举说明。

Vert.x指标

  • vertx.event-loop-size - 类型:Gauge(测量值)含义:event loop线程池的线程数量。

  • vertx.worker-pool-size - 类型:Gauge(测量值)含义:worker线程池的线程数量。

  • vertx.cluster-host - 类型:Gauge(测量值)含义:集群主机的设置值。

  • vertx.cluster-port - 类型:Gauge(测量值)含义:集群接口的设置值。

  • vertx.verticles - 类型:Counter(计数器)含义:当前已部署的verticles数量。

  • vertx.verticles.<verticle-name> - 类型:Counter(计数器)含义:<verticle-name>指定名称的verticle部署数量。

Event bus 指标

基础名称: vertx.eventbus

  • handlers - 类型:Counter(计数器)含义: event bus中已注册handler的数量。

  • handlers.myaddress - 类型:Timer(计时器)含义:名为myaddress的handler出个单个messages的速率。

  • messages.bytes-read - 类型:Meter(仪表)含义:获取远程信息的字节数总量。

  • messages.bytes-written - 类型:Meter(仪表)含义:发送到远程地址的信息数据总量。

  • messages.pending - 类型:Counter(计数器)含义:已经被eventbus接受,但是还未被handler处理的信息数。

  • messages.pending-local - 类型:Counter(计数器)含义:由本地发送的已经被eventbus接受,但是还未被handler处理的信息数。

  • messages.pending-remote - 类型:Counter(计数器)含义:由远程发送的已经被eventbus接受,但是还未被handler处理的信息数。

  • messages.received - 类型:ThroughputMeter(吞吐量统计)含义:表示接受消息条目数的速率。

  • messages.received-local - 类型:ThroughputMeter(吞吐量统计)含义:表示接受本地消息条目数的速率。

  • messages.received-remote - 类型:ThroughputMeter(吞吐量统计)含义:表示接受远程消息条目数的速率。

  • messages.delivered - 类型:ThroughputMeter(吞吐量统计)含义:表示消息被传递到一个处理程序的速率。

  • messages.delivered-local - 类型:ThroughputMeter(吞吐量统计)含义:表示本地消息被传递到一个handler的速率。

  • messages.delivered-remote - 类型:ThroughputMeter(吞吐量统计)含义:表示远程消息被传递到一个handler的速率。

  • messages.sent - 类型:ThroughputMeter(吞吐量统计)含义:表示消息被发送的速率。

  • messages.sent-local - 类型:ThroughputMeter(吞吐量统计)含义:表示消息被发送到本地的速率。

  • messages.sent-remote - 类型:ThroughputMeter(吞吐量统计)含义:表示消息被发送到远程服务的速率。

  • messages.published - 类型:ThroughputMeter(吞吐量统计)含义:表示发布消息的速率。

  • messages.published-local - 类型:ThroughputMeter(吞吐量统计)含义:表示发布本地消息的速率。

  • messages.published-remote - 类型:ThroughputMeter(吞吐量统计)含义:表示发布远程消息的速率。

  • messages.reply-failures - 类型:Meter含义:表示回复失败的频率。

Http 服务指标

基础名称: vertx.http.servers.<host>:<port>

Http 服务的数据包括 Net服务的指标 加上以下指标:

  • requests - 类型:Throughput Timer(吞吐量计时器)含义:单个请求及其出现的频率。

  • <http-method>-requests - 类型:Throughput Timer(吞吐量计时器)含义:指定由<http-method>(PUT、GET、POST等)表示方法获取的请求及其频率。

    • 例如: get-requestspost-requests

  • <http-method>-requests./<uri> - A Throughput Timer(吞吐量计时器)含义:指定由<http-method>表示的方法和/<uri>表示的路径的请求内容及其频率。

    • 例如: get-requests./some/uripost-requests./some/uri?foo=bar

  • responses-1xx - 类型:ThroughputMeter(吞吐量统计)含义:1xx响应的频次。

  • responses-2xx - 类型:ThroughputMeter(吞吐量统计)含义:2xx响应的频次。

  • responses-3xx - 类型:ThroughputMeter(吞吐量统计)含义:3xx响应的频次。

  • responses-4xx - 类型:ThroughputMeter(吞吐量统计)含义:4xx响应的频次。

  • responses-5xx - 类型:ThroughputMeter(吞吐量统计)含义:5xx响应的频次。

  • open-websockets - 类型:Counter(计数器)含义:打开网络套接字的连接个数。

  • open-websockets.<remote-host> - 类型:Counter(计数器)含义:连接到<remote-host>指定的地址打开网络套接字的连接个数。

Net 服务指标

基础名称: vertx.net.servers.<host>:<port>

  • open-netsockets - 类型:Counter(计数器)含义:打开net socket的连接数。

  • open-netsockets.<remote-host> - 类型:Counter(计数器)含义:连接到指定的远程主机所打开的net socket连接数。

  • connections - 类型:Timer(计时器)含义:创建连接的频率。

  • exceptions - 类型:Counter(计数器)含义:出现异常的次数。

  • bytes-read - 类型:Histogram(柱状图)含义:读取的字节数。

  • bytes-written - 类型:Histogram(柱状图)含义:写入的字节数。

池指标(Pool metrics)

基础名称: vertx.pool.<type>.<name>。这里的type表示池类型(例如 workerdatasource), name表示池的名称(例如 vert.x-worker-thread)。

类型为worker的线程池是用于阻塞运行的工作线程池,Vert.x将其用于vert.x-worker-thread线程或vert.x-internal-blocking线程。 名为worker的执行线程都使用WorkerExecutor来创建。

数据源(Datasource)使用Vert.x的JDBC客户端创建,名为datasource.

  • queue-delay - 类型:Timer(计时器)含义:测量获取某个资源的等待时间,例如在队列中的等待时间。

  • queue-size - 类型:Counter(计数器)含义:在队列中等待的资源数。

  • usage - 类型:Timer(计时器)含义:测量某个资源被持续使用的时间。

  • in-use - 类型:Counter(计数器)含义:使用资源的实际数量。

  • pool-ratio - 类型:Gauge(测量值)含义:已使用的资源和池规模的比率。

  • max-pool-size - 类型:Gauge(测量值)含义:池的最大规模。

当池的最大规模没有声明时,pool-ratiomax_pool_size将没有任何数据。

除了以上服务器端的指标之外,Vertx还包括一些客户端指标,有需要可以去官网查看。

图形化展示指标数据

在收集到各种指标数据之后,如果只能输出到console看各字符串就太没意思了。在互联网时代,必须有牛逼闪闪的图形统计工具啊,下面将介绍使用开源项目呈现数据报表。

官网介绍了2个开源工具来实现呈现报表的功能——Jolokia和Hawtio。

Jolokia代理

Jolokia 是按照JSR-160的要求实现JMX-HTTP桥接的工具。按照官网的说法,有非常多的平台使用了Jolokia,并且Jolokia在处理远程连接时提供了更安全的支持。(另外一篇博文介绍了Jolokia,想了解点这里:http://my.oschina.net/chkui/blog/708639

首先,需要像下面这样创建一个Vertx实例:

Vertx vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(
    new DropwizardMetricsOptions()
        .setEnabled(true)
        .setJmxEnabled(true)
        .setJmxDomain("vertx-metrics")));

Domain 参数是可以修改的,这个参数会影响Hawtio的服务的浏览名称,下文会有说明。完成这个配并启用统计功能后,vertx-dropwizard-metric会暴露本地的的Mbean服务接口,Jolokia可以通过这个接口获取指标数据。(以上配置也可以用于Vert.x的集群环境。

然后,我们插入Jolokia来做桥接功能。

首先到官网去下载最新的代理包<点我下载>。下载完成后,需要嵌入到我们自己的应用中,像下面这样在java启动命令中增加以下参数来植入Jolokia代理:

-javaagent:%jolokia_home%/jolokia-jvm-<version>-agent.jar=port=7777,host=localhost

下面是完整的例子:

java -javaagent:D:/soft/jolokia/jolokia-jvm-1.3.3-agent.jar=port=7777,host=localhost com.a.b.c.runApp

在eclipse下,右键->[Debug|Run] As->[Debug|Run] Config。弹出的选项卡中,选择Arguments。然后在VM arguments中增加代理参数,如下图:

Vert.x Dropwizard指标数据监控

然后点击右下角的Debug启动。如果启动成功,会在控制台输出。

I> No access restrictor found, access to any MBean is allowed
Jolokia: Agent started with URL http://127.0.0.1:7777/jolokia/

至此。我们的Vertx实例成功启动,启动的同时开启了:

  1. Dropwizard Metrics用于指标收集;
  2. Jmx暴露桥接接口;
  3. Jolokia提供指标数据Rest接口;

随后,我们需要添加一个Hawtio来获取Jolokia暴露的接口数据。

Hawtio展示指标数据

Hawtio看做一个web服务即可,他按照servlet规范开发,是个标准的web服务。他功能就是从暴露的Jolokia接口中读取指标数据,然后以图像化的方式呈现给用户。下面说明如何搭建好Hawtio服务。

首先下载Hawtio的war包<点我下载>。

然后将war包放置到web容器中。Hawtio支持多种servlet规范的web容器,如中国javaer最喜欢的tomcat和jetty,还有Karaf 、Wildfly(Jboss)等。每种容器的配置都有些许不同,我们这里只说明如何配置Tomcat,其他容器的配置方法见Hawtio的配置说明

使用的tomcat并没有多少配置,如果不需要管理用户权限的话,把download的war包直接丢到webapps里启动tomcat就可以看到以下页面了:

Vert.x Dropwizard指标数据监控

然后点击Connect栏,可以看到以下内容:

Vert.x Dropwizard指标数据监控

在表单中填写连接内容:Name随便取一个即可、Scheme选择默认的http、Host填写主机地址(本例是在本地运行,使用127.0.0.1或者localhost)、端口使用前面启动Jolokia使用的端口、Path选择Jolokia。然后点击Connect to remote server。

连接成功后点击左上角导航栏里的JMX(其他菜单可以看到cpu、内存、线程的使用情况等)。下图就是Hawtio图形化的指标数据,提供多种图标(Chart)。这里的文件夹名称“vertx-metrics”就是在上面代码 setJmxDomain("vertx-metrics") 中命名的domian名称。

Vert.x Dropwizard指标数据监控   

Hawtio除了展示Vertx相关的所有数据外,还可以看到Jvm相关的其他数据,例如Cpu、堆、栈、线程池等。如果需要的话,还可以把自己定义更多的监控数据传递到Hawtio显示。

Hawtio权限管理

上面例子说明了如何使用Hawtio展示指标数据。但是任何使用者都可以查看到相关数据。Hawtio提供了开箱即用的用户权限控制功能。需要针对不同的容器环境进行配置。这里以tomcat为例。

在tomcat的启动脚本中添加下面的参数:

linux:

export CATALINA_OPTS='-Dhawtio.authenticationEnabled=true -Dhawtio.role=manager'

windows:

set JAVA_OPTS=-Dhawtio.authenticationEnabled=true

然后Hawtio会使用tomcat的用户权限来控制自身的用户权限。也就是说,如果在 %TOMCAT_HOME%/conf/tomcat-users.xml中配置了以下内容:

<user username="user" password="passwd" roles="tomcat"/>

则可以使用user/passwd作为账号密码登录Hawtio。

配置生效后输入Hawtio的地址会跳转到下面这个页面。

Vert.x Dropwizard指标数据监控

只有登录成功才能继续访问其他资源。

现在,你的Vert.x已经有了一个牛逼闪闪的图像化监控工具。当然,本文提到的工具不仅仅只能用于监控Vert.x,只要稍加改动,可以监控JVM的方方面面。