Netflix Servo简介

2023/05/12

1. 概述

Netflix Servo是Java应用程序的度量工具。Servo 类似于Dropwizard Metrics,但更简单。它仅利用 JMX 来提供用于公开和发布应用程序指标的简单接口。

在本文中,我们将介绍 Servo 提供的功能以及我们如何使用它来收集和发布应用程序指标。

2.Maven依赖

在我们深入实际实现之前,让我们将[Servo](https://search.maven.org/classic/#search ga 1 a%3A”servo-core”)依赖项添加到pom.xml文件中:
<dependency>
    <groupId>com.netflix.servo</groupId>
    <artifactId>servo-core</artifactId>
    <version>0.12.16</version>
</dependency>
此外,还有很多可用的扩展,例如[Servo-Apache](https://search.maven.org/classic/#search ga 1 servo-apache)、[Servo-AWS](https://search.maven.org/classic/#search ga 1 servo-aws)等,我们以后可能会需要它们。这些扩展的最新版本也可以在[Maven Central](https://search.maven.org/classic/#search ga 1 g%3A”com.netflix.servo”)上找到。

3.收集指标

首先,让我们看看如何从我们的应用程序中收集指标。

Servo 提供四种主要的指标类型:CounterGaugeTimerInformational

3.1. 指标类型 –计数器

计数器用于记录增量。常用的实现是BasicCounter StepCounterPeakRateCounter

BasicCounter做了计数器应该做的事情,简单明了:

Counter counter = new BasicCounter(MonitorConfig.builder("test").build());
assertEquals("counter should start with 0", 0, counter.getValue().intValue());

counter.increment();
 
assertEquals("counter should have increased by 1", 1, counter.getValue().intValue());

counter.increment(-1);
 
assertEquals("counter should have decreased by 1", 0, counter.getValue().intValue());

PeakRateCounter返回轮询间隔期间给定秒的最大计数:

Counter counter = new PeakRateCounter(MonitorConfig.builder("test").build());
assertEquals(
  "counter should start with 0", 
  0, counter.getValue().intValue());

counter.increment();
SECONDS.sleep(1);

counter.increment();
counter.increment();

assertEquals("peak rate should have be 2", 2, counter.getValue().intValue());

与其他计数器不同,StepCounter记录上一次轮询间隔的每秒速率:

System.setProperty("servo.pollers", "1000");
Counter counter = new StepCounter(MonitorConfig.builder("test").build());
 
assertEquals("counter should start with rate 0.0", 0.0, counter.getValue());

counter.increment();
SECONDS.sleep(1);

assertEquals(
  "counter rate should have increased to 1.0", 
  1.0, counter.getValue());

请注意,我们在上面的代码中将servo.pollers设置为1000。那就是将轮询间隔设置为1秒,而不是默认的60秒和10秒的间隔。稍后我们将对此进行更多介绍。

3.2. 指标类型 –仪表

Gauge是一个返回当前值的简单监视器。提供了BasicGauge MinGauge MaxGaugeNumberGauges

BasicGauge调用Callable来获取当前值。我们可以获得集合的大小、BlockingQueue的最新值或任何需要少量计算的值。

Gauge<Double> gauge = new BasicGauge<>(MonitorConfig.builder("test")
  .build(), () -> 2.32);
 
assertEquals(2.32, gauge.getValue(), 0.01);

MaxGauge和MinGauge分别用于跟踪最大值和最小值:

MaxGauge gauge = new MaxGauge(MonitorConfig.builder("test").build());
assertEquals(0, gauge.getValue().intValue());

gauge.update(4);
assertEquals(4, gauge.getCurrentValue(0));

gauge.update(1);
assertEquals(4, gauge.getCurrentValue(0));

NumberGauge ( LongGauge , DoubleGauge ) 包装提供的Number ( Long , Double )。要使用这些量规收集指标,我们必须确保Number是线程安全的。

3.3. 指标类型 –计时器

计时器有助于测量特定事件的持续时间。默认实现是BasicTimer StatsTimerBucketTimer

BasicTimer记录总时间、计数等简单统计信息:

BasicTimer timer = new BasicTimer(MonitorConfig.builder("test").build(), SECONDS);
Stopwatch stopwatch = timer.start();

SECONDS.sleep(1);
timer.record(2, SECONDS);
stopwatch.stop();

assertEquals("timer should count 1 second", 1, timer.getValue().intValue());
assertEquals("timer should count 3 seconds in total", 
  3.0, timer.getTotalTime(), 0.01);
assertEquals("timer should record 2 updates", 2, timer.getCount().intValue());
assertEquals("timer should have max 2", 2, timer.getMax(), 0.01);

StatsTimer通过在轮询间隔之间进行采样来提供更丰富的统计信息:

System.setProperty("netflix.servo", "1000");
StatsTimer timer = new StatsTimer(MonitorConfig
  .builder("test")
  .build(), new StatsConfig.Builder()
  .withComputeFrequencyMillis(2000)
  .withPercentiles(new double[] { 99.0, 95.0, 90.0 })
  .withPublishMax(true)
  .withPublishMin(true)
  .withPublishCount(true)
  .withPublishMean(true)
  .withPublishStdDev(true)
  .withPublishVariance(true)
  .build(), SECONDS);
Stopwatch stopwatch = timer.start();

SECONDS.sleep(1);
timer.record(3, SECONDS);
stopwatch.stop();

stopwatch = timer.start();
timer.record(6, SECONDS);
SECONDS.sleep(2);
stopwatch.stop();

assertEquals("timer should count 12 seconds in total", 
  12, timer.getTotalTime());
assertEquals("timer should count 12 seconds in total", 
  12, timer.getTotalMeasurement());
assertEquals("timer should record 4 updates", 4, timer.getCount());
assertEquals("stats timer value time-cost/update should be 2", 
  3, timer.getValue().intValue());

final Map<String, Number> metricMap = timer.getMonitors().stream()
  .collect(toMap(monitor -> getMonitorTagValue(monitor, "statistic"),
    monitor -> (Number) monitor.getValue()));
 
assertThat(metricMap.keySet(), containsInAnyOrder(
  "count", "totalTime", "max", "min", "variance", "stdDev", "avg", 
  "percentile_99", "percentile_95", "percentile_90"));

BucketTimer提供了一种通过分桶值范围来获取样本分布的方法:

BucketTimer timer = new BucketTimer(MonitorConfig
  .builder("test")
  .build(), new BucketConfig.Builder()
  .withBuckets(new long[] { 2L, 5L })
  .withTimeUnit(SECONDS)
  .build(), SECONDS);

timer.record(3);
timer.record(6);

assertEquals(
  "timer should count 9 seconds in total",
  9, timer.getTotalTime().intValue());
 
Map<String, Long> metricMap = timer.getMonitors().stream()
  .filter(monitor -> monitor.getConfig().getTags().containsKey("servo.bucket"))
  .collect(toMap(
    m -> getMonitorTagValue(m, "servo.bucket"),
    m -> (Long) m.getValue()));

assertThat(metricMap, allOf(hasEntry("bucket=2s", 0L), hasEntry("bucket=5s", 1L),
  hasEntry("bucket=overflow", 1L)));

要跟踪可能持续数小时的长时间操作,我们可以使用复合监视器DurationTimer

3.4. 指标类型 -信息

此外,我们还可以利用Informational监视器记录描述性信息,以帮助调试和诊断。唯一的实现是BasicInformational,它的用法再简单不过了:

BasicInformational informational = new BasicInformational(
  MonitorConfig.builder("test").build());
informational.setValue("information collected");

3.5. 监控注册表

度量类型都是Monitor类型,它是Servo的基础。我们现在知道各种工具收集原始指标,但要报告数据,我们需要注册这些监视器。

请注意,每个配置的监视器都应注册一次且仅注册一次以确保指标的正确性。所以我们可以使用单例模式注册监视器。

大多数时候,我们可以使用DefaultMonitorRegistry来注册监视器:

Gauge<Double> gauge = new BasicGauge<>(MonitorConfig.builder("test")
  .build(), () -> 2.32);
DefaultMonitorRegistry.getInstance().register(gauge);

如果我们想动态注册一个监视器,可以使用DynamicTimerDynamicCounter :

DynamicCounter.increment("monitor-name", "tag-key", "tag-value");

请注意,每次更新值时,动态注册都会导致昂贵的查找操作。

Servo 还提供了几种辅助方法来注册在对象中声明的监视器:

Monitors.registerObject("testObject", this);
assertTrue(Monitors.isObjectRegistered("testObject", this));

方法registerObject将使用反射来添加由注解@Monitor声明的所有监视器实例,并添加由@MonitorTags声明的标签:

@Monitor(
  name = "integerCounter",
  type = DataSourceType.COUNTER,
  description = "Total number of update operations.")
private AtomicInteger updateCount = new AtomicInteger(0);

@MonitorTags
private TagList tags = new BasicTagList(
  newArrayList(new BasicTag("tag-key", "tag-value")));

@Test
public void givenAnnotatedMonitor_whenUpdated_thenDataCollected() throws Exception {
    System.setProperty("servo.pollers", "1000");
    Monitors.registerObject("testObject", this);
    assertTrue(Monitors.isObjectRegistered("testObject", this));

    updateCount.incrementAndGet();
    updateCount.incrementAndGet();
    SECONDS.sleep(1);

    List<List<Metric>> metrics = observer.getObservations();
 
    assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
 
    Iterator<List<Metric>> metricIterator = metrics.iterator();
    metricIterator.next(); //skip first empty observation
 
    while (metricIterator.hasNext()) {
        assertThat(metricIterator.next(), hasItem(
          hasProperty("config", 
          hasProperty("name", is("integerCounter")))));
    }
}

4. 发布指标

通过收集到的指标,我们可以将其发布为任何格式,例如在各种数据可视化平台上渲染时间序列图。要发布指标,我们需要定期从监视器观察中轮询数据。

4.1. 度量轮询器

MetricPoller用作指标获取器。我们可以获取MonitorRegistries JVM JMX的指标。在扩展的帮助下,我们可以轮询Apache 服务器状态Tomcat 指标等指标

MemoryMetricObserver observer = new MemoryMetricObserver();
PollRunnable pollRunnable = new PollRunnable(new JvmMetricPoller(),
  new BasicMetricFilter(true), observer);
PollScheduler.getInstance().start();
PollScheduler.getInstance().addPoller(pollRunnable, 1, SECONDS);

SECONDS.sleep(1);
PollScheduler.getInstance().stop();
List<List<Metric>> metrics = observer.getObservations();

assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
List<String> keys = extractKeys(metrics);
 
assertThat(keys, hasItems("loadedClassCount", "initUsage", "maxUsage", "threadCount"));

这里我们创建了一个JvmMetricPoller来轮询 JVM 的指标。将轮询器添加到调度程序时,我们让轮询任务每秒运行一次。系统默认轮询器配置在Pollers中定义,但我们可以指定轮询器与系统属性servo.pollers一起使用。

4.2. 指标观察者

当轮询指标时,注册的MetricObservers的观察将被更新。

默认提供的MetricObservers是MemoryMetricObserverFileMetricObserverAsyncMetricObserver。我们已经在前面的代码示例中展示了如何使用MemoryMetricObserver 。

目前,有几个有用的扩展可用:

我们可以实现自定义的MetricObserver以将应用程序指标发布到我们认为合适的位置。唯一需要关心的是处理更新的指标:

public class CustomObserver extends BaseMetricObserver {

    //...

    @Override
    public void updateImpl(List<Metric> metrics) {
        //TODO
    }
}

4.3. 发布到 Netflix Atlas

Atlas是 Netflix 的另一个与指标相关的工具。它是用于管理维度时间序列数据的工具,是发布我们收集的指标的理想场所。

现在,我们将演示如何将指标发布到 Netflix Atlas。

首先,让我们将[servo-atlas](https://search.maven.org/classic/#search ga 1 g%3A”com.netflix.servo” AND a%3A”servo-atlas”)依赖项附加到pom.xml:
<dependency>
      <groupId>com.netflix.servo</groupId>
      <artifactId>servo-atlas</artifactId>
      <version>${netflix.servo.ver}</version>
</dependency>

<properties>
    <netflix.servo.ver>0.12.17</netflix.servo.ver>
</properties>

这个依赖包括一个AtlasMetricObserver来帮助我们发布指标到Atlas。

然后,我们将设置一个 Atlas 服务器:

$ curl -LO 'https://github.com/Netflix/atlas/releases/download/v1.4.4/atlas-1.4.4-standalone.jar'
$ curl -LO 'https://raw.githubusercontent.com/Netflix/atlas/v1.4.x/conf/memory.conf'
$ java -jar atlas-1.4.4-standalone.jar memory.conf

为了节省我们的测试时间,让我们在memory.conf 中将步长设置为 1 秒,这样我们就可以生成一个包含足够详细指标的时间序列图。

AtlasMetricObserver需要一个简单的配置和一个标签列表。给定标签的指标将被推送到 Atlas:

System.setProperty("servo.pollers", "1000");
System.setProperty("servo.atlas.batchSize", "1");
System.setProperty("servo.atlas.uri", "http://localhost:7101/api/v1/publish");
AtlasMetricObserver observer = new AtlasMetricObserver(
  new BasicAtlasConfig(), BasicTagList.of("servo", "counter"));

PollRunnable task = new PollRunnable(
  new MonitorRegistryMetricPoller(), new BasicMetricFilter(true), observer);

使用PollRunnable任务启动PollScheduler后,我们可以自动将指标发布到 Atlas:

Counter counter = new BasicCounter(MonitorConfig
  .builder("test")
  .withTag("servo", "counter")
  .build());
DefaultMonitorRegistry
  .getInstance()
  .register(counter);
assertThat(atlasValuesOfTag("servo"), not(containsString("counter")));

for (int i = 0; i < 3; i++) {
    counter.increment(RandomUtils.nextInt(10));
    SECONDS.sleep(1);
    counter.increment(-1  RandomUtils.nextInt(10));
    SECONDS.sleep(1);
}

assertThat(atlasValuesOfTag("servo"), containsString("counter"));

基于指标,我们可以使用Atlas 的图形 API生成折线图:

图形

5.总结

在本文中,我们介绍了如何使用 Netflix Servo 收集和发布应用指标。

如果还没有阅读我们对 Dropwizard Metrics 的介绍,请在此处查看以与 Servo 进行快速比较。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章