天天看點

Metrics介紹引用Metric庫RegistryGauge (儀表)Counter (計數器)Meter ()Histogram (直方圖)Timer (計時器)Health Check (健康檢查)JMX報表HTTP報表其它報表MetricSet一些子產品第三方庫Metrics for Spring

Metrics可以為你的代碼的運作提供無與倫比的洞察力。作為一款監控名額的度量類庫,它提供了很多子產品可以為第三方庫或者應用提供輔助統計資訊, 比如Jetty, Logback, Log4j, Apache HttpClient, Ehcache, JDBI, Jersey, 它還可以将度量資料發送給Ganglia和Graphite以提供圖形化的監控。

Metrics提供了Gauge、Counter、Meter、Histogram、Timer等度量工具類以及Health Check功能。

Metrics介紹引用Metric庫RegistryGauge (儀表)Counter (計數器)Meter ()Histogram (直方圖)Timer (計時器)Health Check (健康檢查)JMX報表HTTP報表其它報表MetricSet一些子產品第三方庫Metrics for Spring

引用Metric庫

将metrics-core加入到maven pom.xml中:

<dependencies>
    <dependency>
        <groupId>com.codahale.metrics</groupId>
        <artifactId>metrics-core</artifactId>
        <version>${metrics.version}</version>
    </dependency>
</dependencies>
           

metrics.version

 設定為metrics最新的版本。

現在你可以在你的程式代碼中加入一些度量了。

Registry

Metric的中心部件是

MetricRegistry

。 它是程式中所有度量metric的容器。讓我們接着在代碼中加入一行:

final MetricRegistry metrics = new MetricRegistry();
           

Gauge (儀表)

Gauge

代表一個度量的即時值。 當你開汽車的時候, 目前速度是Gauge值。 你測體溫的時候, 體溫計的刻度是一個Gauge值。 當你的程式運作的時候, 記憶體使用量和CPU占用率都可以通過Gauge值來度量。

比如我們可以檢視一個隊列目前的size。

public class QueueManager {
    private final Queue queue;
    public QueueManager(MetricRegistry metrics, String name) {
        this.queue = new Queue();
        metrics.register(MetricRegistry.name(QueueManager.class, name, "size"),
                         new Gauge<Integer>() {
                             @Override
                             public Integer getValue() {
                                 return queue.size();
                             }
                         });
    }
}
           

registry

 中每一個

metric

都有唯一的名字。 它可以是以.連接配接的字元串。 如"things.count" 和 "com.colobu.Thing.latency"。 

MetricRegistry

 提供了一個靜态的輔助方法用來生成這個名字:

MetricRegistry.name(QueueManager.class, "jobs", "size")
           

生成的name為

com.colobu.QueueManager.jobs.size

實際程式設計中對于隊列或者類似隊列的資料結構,你不會簡單的度量queue.size(), 因為在java.util和java.util.concurrent包中大部分的queue的#size是O(n),這意味的調用此方法會有性能的問題, 更深一步,可能會有lock的問題。
           

RatioGauge可以計算兩個Gauge的比值。 Meter和Timer可以參考下面的代碼建立。下面的代碼用來計算計算命中率 (hit/call)。

public class CacheHitRatio extends RatioGauge {
    private final Meter hits;
    private final Timer calls;
    public CacheHitRatio(Meter hits, Timer calls) {
        this.hits = hits;
        this.calls = calls;
    }
    @Override
    public Ratio getValue() {
        return Ratio.of(hits.oneMinuteRate(),
                        calls.oneMinuteRate());
    }
}
           

CachedGauge可以緩存耗時的測量。DerivativeGauge可以引用另外一個Gauage。

Counter (計數器)

Counter

是一個

AtomicLong

執行個體, 可以增加或者減少值。 例如,可以用它來計數隊列中加入的Job的總數。 

private final Counter pendingJobs = metrics.counter(name(QueueManager.class, "pending-jobs"));
public void addJob(Job job) {
    pendingJobs.inc();
    queue.offer(job);
}
public Job takeJob() {
    pendingJobs.dec();
    return queue.take();
}
           

和上面Gauage不同,這裡我們使用的是metrics.counter方法而不是metrics.register方法。 使用metrics.counter更簡單。

Meter ()

Meter

用來計算事件的速率。 例如 request per second。 還可以提供1分鐘,5分鐘,15分鐘不斷更新的平均速率。

private final Meter requests = metrics.meter(name(RequestHandler.class, "requests"));
public void handleRequest(Request request, Response response) {
    requests.mark();
    // etc
}
           

Histogram (直方圖)

Histogram

可以為資料流提供統計資料。 除了最大值,最小值,平均值外,它還可以測量 中值(median),百分比比如XX%這樣的Quantile資料 。

private final Histogram responseSizes = metrics.histogram(name(RequestHandler.class, "response-sizes");
public void handleRequest(Request request, Response response) {
    // etc
    responseSizes.update(response.getContent().length);
}
           

這個例子用來統計response的位元組數。

Metrics提供了一批的Reservoir實作,非常有用。例如SlidingTimeWindowReservoir 用來統計最新N個秒(或其它時間單元)的資料。

Timer (計時器)

Timer

用來測量一段代碼被調用的速率和用時。

private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
public String handleRequest(Request request, Response response) {
    final Timer.Context context = responses.time();
    try {
        // etc;
        return "OK";
    } finally {
        context.stop();
    }
}
           

這段代碼用來計算中間的代碼用時以及request的速率。

Health Check (健康檢查)

Metric

還提供了服務健康檢查能力, 由

metrics-healthchecks

子產品提供。

先建立一個

HealthCheckRegistry

執行個體。

final HealthCheckRegistry healthChecks = new HealthCheckRegistry();
           

再實作一個

HealthCheck

子類, 用來檢查資料庫的狀态。

public class DatabaseHealthCheck extends HealthCheck {
    private final Database database;
    public DatabaseHealthCheck(Database database) {
        this.database = database;
    }
    @Override
    public HealthCheck.Result check() throws Exception {
        if (database.isConnected()) {
            return HealthCheck.Result.healthy();
        } else {
            return HealthCheck.Result.unhealthy("Cannot connect to " + database.getUrl());
        }
    }
}
           

注冊一下。

healthChecks.register("mysql", new DatabaseHealthCheck(database));
           

最後運作健康檢查并檢視檢查結果。

final Map<String, HealthCheck.Result> results = healthChecks.runHealthChecks();
for (Entry<String, HealthCheck.Result> entry : results.entrySet()) {
    if (entry.getValue().isHealthy()) {
        System.out.println(entry.getKey() + " is healthy");
    } else {
        System.err.println(entry.getKey() + " is UNHEALTHY: " + entry.getValue().getMessage());
        final Throwable e = entry.getValue().getError();
        if (e != null) {
            e.printStackTrace();
        }
    }
}
           

Metric

内置一個ThreadDeadlockHealthCheck, 它使用java内置的線程死鎖檢查方法來檢查程式中是否有死鎖。

JMX報表

通過JMX報告Metric。

final JmxReporter reporter = JmxReporter.forRegistry(registry).build();
reporter.start();
           

一旦啟動, 所有registry中注冊的metric都可以通過JConsole或者VisualVM檢視 (通過MBean插件)。

HTTP報表

Metric也提供了一個servlet (AdminServlet)提供JSON風格的報表。它還提供了單一功能的servlet (MetricsServlet, HealthCheckServlet, ThreadDumpServlet, PingServlet)。

你需要在pom.xml加入metrics-servlets。

<dependency>
    <groupId>com.codahale.metrics</groupId>
    <artifactId>metrics-servlets</artifactId>
    <version>${metrics.version}</version>
</dependency>
           

其它報表

除了JMX和HTTP, metric還提供其它報表。

  • STDOUT, using ConsoleReporter from metrics-core
final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
                                                .convertRatesTo(TimeUnit.SECONDS)
                                                .convertDurationsTo(TimeUnit.MILLISECONDS)
                                                .build();
reporter.start(1, TimeUnit.MINUTES);
           
  • CSV files, using CsvReporter from metrics-core
final CsvReporter reporter = CsvReporter.forRegistry(registry)
                                        .formatFor(Locale.US)
                                        .convertRatesTo(TimeUnit.SECONDS)
                                        .convertDurationsTo(TimeUnit.MILLISECONDS)
                                        .build(new File("~/projects/data/"));
reporter.start(1, TimeUnit.SECONDS);
           
  • SLF4J loggers, using Slf4jReporter from metrics-core
final Slf4jReporter reporter = Slf4jReporter.forRegistry(registry)
                                            .outputTo(LoggerFactory.getLogger("com.example.metrics"))
                                            .convertRatesTo(TimeUnit.SECONDS)
                                            .convertDurationsTo(TimeUnit.MILLISECONDS)
                                            .build();
reporter.start(1, TimeUnit.MINUTES);
           
  • Ganglia, using GangliaReporter from metrics-ganglia
final GMetric ganglia = new GMetric("ganglia.example.com", 8649, UDPAddressingMode.MULTICAST, 1);
final GangliaReporter reporter = GangliaReporter.forRegistry(registry)
                                                .convertRatesTo(TimeUnit.SECONDS)
                                                .convertDurationsTo(TimeUnit.MILLISECONDS)
                                                .build(ganglia);
reporter.start(1, TimeUnit.MINUTES);
           
  • Graphite, using GraphiteReporter from metrics-graphite

MetricSet

可以将一組Metric組織成一組便于重用。

final Graphite graphite = new Graphite(new InetSocketAddress("graphite.example.com", 2003));
final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
                                                  .prefixedWith("web1.example.com")
                                                  .convertRatesTo(TimeUnit.SECONDS)
                                                  .convertDurationsTo(TimeUnit.MILLISECONDS)
                                                  .filter(MetricFilter.ALL)
                                                  .build(graphite);
reporter.start(1, TimeUnit.MINUTES);
           

一些子產品

  • metrics-json提供了json格式的序列化。
  • 以及為其它庫提供度量的能力
  • metrics-ehcache
  • metrics-httpclient
  • metrics-jdbi
  • metrics-jersey
  • metrics-jetty
  • metrics-log4j
  • metrics-logback
  • metrics-jvm
  • metrics-servlet 注意不是metrics-servlets

第三方庫

  • metrics-librato 提供Librato Metrics報表
  • Metrics Spring Integration 提供了Spring的內建
  • sematext-metrics-reporter 提供了SPM報表.
  • wicket-metrics提供Wicket應用.
  • metrics-guice 提供Guice內建.
  • metrics-scala 提供了為Scala優化的API.

這裡重點介紹一下Metrics for Spring

Metrics for Spring

這個庫為Spring增加了Metric庫, 提供基于XML或者注解方式。

  • 可以使用注解建立metric和代理類。 @Timed, @Metered, @ExceptionMetered, @Counted
  • 為注解了 @Gauge 和 @CachedGauge的bean注冊Gauge
  • 為@Metric注解的字段自動裝配
  • 注冊HealthCheck
  • 通過XML配置産生報表
  • 通過XML注冊metric和metric組

你需要在pom.xml加入

<dependency>
    <groupId>com.ryantenney.metrics</groupId>
    <artifactId>metrics-spring</artifactId>
    <version>3.0.1</version>
</dependency>
           

基本用法

  • XML風格的配置
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:metrics="http://www.ryantenney.com/schema/metrics"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.ryantenney.com/schema/metrics
           http://www.ryantenney.com/schema/metrics/metrics-3.0.xsd">
    <!-- Registry should be defined in only one context XML file -->
    <metrics:metric-registry id="metrics" />
    <!-- annotation-driven must be included in all context files -->
    <metrics:annotation-driven metric-registry="metrics" />
    <!-- (Optional) Registry should be defined in only one context XML file -->
    <metrics:reporter type="console" metric-registry="metrics" period="1m" />
    <!-- (Optional) The metrics in this example require the metrics-jvm jar-->
    <metrics:register metric-registry="metrics">
        <bean metrics:name="jvm.gc" class="com.codahale.metrics.jvm.GarbageCollectorMetricSet" />
        <bean metrics:name="jvm.memory" class="com.codahale.metrics.jvm.MemoryUsageGaugeSet" />
        <bean metrics:name="jvm.thread-states" class="com.codahale.metrics.jvm.ThreadStatesGaugeSet" />
        <bean metrics:name="jvm.fd.usage" class="com.codahale.metrics.jvm.FileDescriptorRatioGauge" />
    </metrics:register>
    <!-- Beans and other Spring config -->
</beans>
           
  • java注解的方式
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
@Configuration
@EnableMetrics
public class SpringConfiguringClass extends MetricsConfigurerAdapter {
    @Override
    public void configureReporters(MetricRegistry metricRegistry) {
        ConsoleReporter
            .forRegistry(metricRegistry)
            .build()
            .start(1, TimeUnit.MINUTES);
    }
}