系统监控-Metric

Metrics is a Java library which gives you unparalleled insight into what your code does in production.
Metrics provides a powerful toolkit of ways to measure the behavior of critical components in your production environment.

简单的说Metrics是一个监控系统性能指标的一个Java库。Cassandra使用它进行指标统计。


在Maven的pom.xml中引入Metrics:

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

Metrics使用非常简单,首先是Metrics的核心类

MetricRegistry类

The centerpiece of Metrics is the MetricRegistry class, which is the container for all your application’s metrics. Go ahead and create a new one:

我们一般直接new出Registry对象就可以完成Registry的创建。

final MetricRegistry metrics = new MetricRegistry();

MetricRegistryclass中维护了一个Metric集合和一个MetricRegistryListener列表

    private final ConcurrentMap<String, Metric> metrics;
    private final List<MetricRegistryListener> listeners;
    /**
     * Creates a new {@link MetricRegistry}.
     */
    public MetricRegistry() {
        this.metrics = buildMap();
        this.listeners = new CopyOnWriteArrayList<MetricRegistryListener>();
    }

Metric的注册、快速创建、获取等等的基本功能,都会维护Metric集合。

// 注册
    public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
        if (metric instanceof MetricSet) {
            registerAll(name, (MetricSet) metric);
        } else {
            final Metric existing = metrics.putIfAbsent(name, metric);
            if (existing == null) {
                onMetricAdded(name, metric);
            } else {
                throw new IllegalArgumentException("A metric named " + name + " already exists");
            }
        }
        return metric;
    }
// 创建+注册 or 获取
    public Meter meter(String name) {
        return getOrAdd(name, MetricBuilder.METERS);
    }
    public Timer timer(String name) {
        return getOrAdd(name, MetricBuilder.TIMERS);
    }
// 获取
    public SortedMap<String, Gauge> getGauges() {
        return getGauges(MetricFilter.ALL);
    }
// etc

JmxReporterstrat的时候会向MetricRegistry注册一个JmxListenerclass,MetricRegistry会将它维护到MetricRegistryListener列表中。 JmxReporter会在后面介绍到,它是统计结果的输出控制类。MetricRegistry会在Metricadd or remove时将Metric传递给所有的JmxListenerJmxListenerMetric的真正使用者。
JmxReporter:

    private final JmxListener listener;

    private JmxReporter(MBeanServer mBeanServer,
                        String domain,
                        MetricRegistry registry,
                        MetricFilter filter,
                        MetricTimeUnits timeUnits, 
                        ObjectNameFactory objectNameFactory) {
        this.registry = registry;
        this.listener = new JmxListener(mBeanServer, domain, filter, timeUnits, objectNameFactory);
    }
    public void start() {
        registry.addListener(listener);
    }

MetricRegistry:

    private void onMetricAdded(String name, Metric metric) {
        for (MetricRegistryListener listener : listeners) {
            notifyListenerOfAddedMetric(listener, metric, name);
        }
    }

Reporter接口

它是Metrics输出控制类的统一接口,它将采集的数据展现到不同的位置。Metrics提供两个实现类JmxReporterScheduledReporter
JmxReporter会通过JMX报告指标,它需要依赖metrics-jmx包

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-jmx</artifactId>
    <version>${metrics.version}</version>
</dependency>
final JmxReporter reporter = JmxReporter.forRegistry(registry).build();
reporter.start();

ScheduledReporter顾名思义,会定时的输出报表。ConsoleReporterSlf4jReporter等等Metric自带的所以Reporter都是继承于它,一般我们自定义的Reporter也会继承它。

A Console Reporter is exactly what it sounds like - report to the console. This reporter will print every second.

ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
       .convertRatesTo(TimeUnit.SECONDS)
       .convertDurationsTo(TimeUnit.MILLISECONDS)
       .build();
   reporter.start(1, TimeUnit.SECONDS);

Metrics

最后来看下Metrics提供了五个基本的Metrics :

  1. Meters(TPS)

A meter measures the rate of events over time (e.g., “requests per second”). In addition to the mean rate, meters also track 1-, 5-, and 15-minute moving averages.

使用起来非常简单,这里暂不多介绍,只从官网复制了些实例代码展示下如何使用:

private final MetricRegistry metrics = new MetricRegistry();
private final Meter requests = metrics.meter("requests");

public void handleRequest(Request request, Response response) {
    requests.mark();
    // etc
}

结果:

19-6-11 10:09:32 ===============================================================

-- Meters ----------------------------------------------------------------------
meter
             count = 56
         mean rate = 1.00 events/second
     1-minute rate = 1.00 events/second
     5-minute rate = 1.00 events/second
    15-minute rate = 1.00 events/second

还有一些常用方法:

    @Override
    public long getCount() {
        return count.sum();
    }
    @Override
    public double getOneMinuteRate() {
        tickIfNecessary();
        return m1Rate.getRate(TimeUnit.SECONDS);
    }
// etc
  1. Gauges
    如果说上面Meters是一个动态的统计的话,Gauges就是一个静态的统计。它能统计出关注对象当时的状态。

A gauge is an instantaneous measurement of a value. For example, we may want to measure the number of pending jobs in a queue:

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();
                             }
                         });
    }
}
MetricRegistry.name(QueueManager.class, "jobs", "size");

它的效率受时间复杂度影响

For most queue and queue-like structures, you won’t want to simply return queue.size(). Most of java.util and java.util.concurrent have implementations of #size() which are O(n), which means your gauge will be slow (potentially while holding a lock).

  1. Counters
    类似于一个AtomicLong
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();
}

官方推荐使用#counter(String)代替#register(String, Metric)创建。

As you can see, the API for counters is slightly different: #counter(String) instead of #register(String, Metric). While you can use register and create your own Counter instance, #counter(String) does all the work for you, and allows you to reuse metrics with the same name.
Also, we’ve statically imported MetricRegistry’s name method in this scope to reduce clutter.

  1. Histograms(直方图)
    直方图是一种用于统计数据的图表,Histogram能统计数据的最小值、最大值、平均值等,还有测量中位数、75位、90位、95位、98位、99位和99.9。

A histogram measures the statistical distribution of values in a stream of data. In addition to minimum, maximum, mean, etc., it also measures median, 75th, 90th, 95th, 98th, 99th, and 99.9th percentiles.

private final Histogram responseSizes = metrics.histogram(name(RequestHandler.class, "response-sizes"));

public void handleRequest(Request request, Response response) {
    // etc
    responseSizes.update(response.getContent().length);
}

有一点在官网上特别提醒:历史数据的大小之间影响到了Histogram的响应速度,所以Histogram都需要设定了默认大小。

This histogram will measure the size of responses in bytes.

Histogram基本功能都是通过内部的Reservoir来实现的。使用#histogram(String)来创建会默认使用ExponentiallyDecayingReservoir,并且他的默认大小是1028。

        MetricBuilder<Histogram> HISTOGRAMS = new MetricBuilder<Histogram>() {
            @Override
            public Histogram newMetric() {
                return new Histogram(new ExponentiallyDecayingReservoir());
            }

            @Override
            public boolean isInstance(Metric metric) {
                return Histogram.class.isInstance(metric);
            }
        };
  1. Timers
    TimerMeter类似,但它的功能比Meter强大很多。Meter只统计次数,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();
    }
}

在使用上,我们需在计时开始处创建Context,出口处调用#context.stop()
简单看下源码,我们能发现Timer内部其实就是使用 MeterHistogram来实现的。

    /**
     * Creates a new {@link Timer} that uses the given {@link Reservoir} and {@link Clock}.
     *
     * @param reservoir the {@link Reservoir} implementation the timer should use
     * @param clock  the {@link Clock} implementation the timer should use
     */
    public Timer(Reservoir reservoir, Clock clock) {
        this.meter = new Meter(clock);
        this.clock = clock;
        this.histogram = new Histogram(reservoir);
    }

    private void update(long duration) {
        if (duration >= 0) {
            histogram.update(duration);
            meter.mark();
        }
    }

Health Checks

Metrics还提供了服务的集中健康检查。
这需要在Maven的pom.xml中引入新的Metrics包:

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

基本使用流程:

  1. 创建各种健康检查的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());
        }
    }
}
  1. HealthCheck注册到HealthCheckRegistry
final HealthCheckRegistry healthChecks = new HealthCheckRegistry();

healthChecks.register("postgres", new DatabaseHealthCheck(database));
  1. 运行HealthCheckRegistry:#runHealthChecks()
final Map<String, HealthCheck.Result> results = healthChecks.runHealthChecks();
  1. 通过遍历返回值判断健康状态。
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();
        }
    }
}

JVM Instrumentation

这是Metrics中对于jvm的监控。

The metrics-jvm module contains a number of reusable gauges and metric sets which allow you to easily instrument JVM internals.

它需要引入对于Jar包:

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

Metrics提供了各种jvm的指标metricMetricSet子类,在子类对ManagementFactory各种获取属性进行了分装,来实现对jvm的监控。

    /**
     * Creates a new set of gauges using the default MXBeans.
     * Caches the information for the given interval and time unit.
     *
     * @param interval cache interval
     * @param unit     cache interval time unit
     */
    public CachedThreadStatesGaugeSet(long interval, TimeUnit unit) {
        this(ManagementFactory.getThreadMXBean(), new ThreadDeadlockDetector(), interval, unit);
    }

    public ClassLoadingGaugeSet() {
        this(ManagementFactory.getClassLoadingMXBean());
    }

    /**
     * Creates a new gauge using the platform OS bean.
     */
    public FileDescriptorRatioGauge() {
        this(ManagementFactory.getOperatingSystemMXBean());
    }

// etc

它支持的指标包括:

  • 运行所有支持的垃圾收集器的计数和已用时间
  • 所有内存池的内存使用情况,包括堆外内存
  • 线程状态的细分,包括死锁
  • 文件描述符用法
  • 缓冲池大小和利用率
    BufferPoolMetricSet:JVM的直接缓冲池和映射缓冲池的数量统计。
    ThreadStatesGaugeSet:检测处于不同状态的线程数和死锁检测的Gauge。
    CachedThreadStatesGaugeSet:同上,多创建了一个CachedGauge
    ClassLoadingGaugeSet:JVM类加载器的Gauge。
    FileDescriptorRatioGauge:是对于操作系统FileDescriptor的一个统计,可以用来监控IO流的释放等等。
    GarbageCollectorMetricSet:可以统计GC的次数和使用时间。
    JmxAttributeGauge:主要统计JVM的runtime,还在Gauge获取了JVM的名称、供应商信息。
    MemoryUsageGaugeSet:对于内存的监控(包含堆内内存和堆外内存)。


Metric官方Document

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容