Введение в Netflix Servo

Введение в Netflix Servo

1. обзор

Netflix Servo - это инструмент метрик для приложений Java. Сервопривод похож наDropwizard Metrics, но намного проще. Он использует JMX только для обеспечения простого интерфейса для представления и публикации метрик приложения.

В этой статье мы расскажем, что предоставляет Servo и как мы можем использовать его для сбора и публикации показателей приложения.

2. Maven Зависимости

Прежде чем мы углубимся в реальную реализацию, давайте добавим зависимостьServo в файлpom.xml:


    com.netflix.servo
    servo-core
    0.12.16

Кроме того, доступно множество расширений, таких какServo-Apache,Servo-AWS и т. Д. Возможно, они понадобятся нам позже. Последние версии этих расширений также можно найти наMaven Central.

3. Собирать показатели

Во-первых, давайте посмотрим, как собирать метрики из нашего приложения.

Сервопривод предоставляет четыре основных типа метрик:Counter,Gauge,Timer, иInformational.

3.1. Типы показателей - счетчик

Counters используются для записи приращения. Обычно используемые реализации:BasicCounter,StepCounter иPeakRateCounter.

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,MaxGauge иNumberGauges.

BasicGauge вызываетCallable для получения текущего значения. Мы можем получить размер коллекции, последнее значениеBlockingQueue или любое значение, требующее небольших вычислений.

Gauge 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. Типы показателей - таймер

Timers помогает измерить продолжительность определенного события. Реализации по умолчанию:BasicTimer,StatsTimer иBucketTimer.

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 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 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. MonitorRegistryс

Все типы метрик относятся к типуMonitor, который является самой основойServo. Теперь мы знаем, какие инструменты собирают необработанные метрики, но чтобы сообщать данные, нам нужно зарегистрировать эти мониторы.

Обратите внимание, что каждый настроенный монитор должен быть зарегистрирован один раз и только один раз, чтобы обеспечить правильность показателей. Таким образом, мы можем зарегистрировать мониторы, используя шаблон Singleton.

В большинстве случаев мы можем использоватьDefaultMonitorRegistry для регистрации мониторов:

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

Если мы хотим динамически зарегистрировать монитор, можно использоватьDynamicTimer иDynamicCounter:

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

Обратите внимание, что динамическая регистрация вызовет дорогостоящую операцию поиска при каждом обновлении значения.

Серво также предоставляет несколько вспомогательных методов для регистрации мониторов, объявленных в объектах:

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

МетодregisterObject будет использовать отражение для добавления всех экземпляровMonitors, объявленных аннотацией@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> metrics = observer.getObservations();

    assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));

    Iterator> 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с

MetricPoller используется как сборщик метрик. Мы можем получить метрикиMonitorRegistries,JVM,JMX. С помощью расширений мы можем опрашивать такие показатели, какApache server status иTomcat metrics.

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> metrics = observer.getObservations();

assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
List keys = extractKeys(metrics);

assertThat(keys, hasItems("loadedClassCount", "initUsage", "maxUsage", "threadCount"));

Здесь мы создалиJvmMetricPoller для опроса метрик JVM. При добавлении опроса в планировщик мы позволяем заданию опроса запускаться каждую секунду. Конфигурации системного опроса по умолчанию определены вPollers, но мы можем указать опросчики для использования с системным свойствомservo.pollers.

4.2. MetricObserverс

При опросе показателей будут обновлены наблюдения зарегистрированныхMetricObservers.

MetricObservers по умолчанию - этоMemoryMetricObserver,FileMetricObserver иAsyncMetricObserver. Мы уже показали, как использоватьMemoryMetricObserver в предыдущем примере кода.

В настоящее время доступно несколько полезных расширений:

  • AtlasMetricObserver: публикация показателей вNetflix Atlas для создания данных временных рядов в памяти для аналитики

  • CloudWatchMetricObserver: отправка показателей вAmazon CloudWatch для мониторинга и отслеживания показателей

  • GraphiteObserver: публикация показателей вGraphite для хранения и построения графиков

Мы можем реализовать настраиваемыйMetricObserver для публикации показателей приложения там, где мы сочтем нужным. Единственное, о чем нужно заботиться, это обработать обновленные метрики:

public class CustomObserver extends BaseMetricObserver {

    //...

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

4.3. Опубликовать в Netflix Atlas

Atlas - еще один инструмент для работы с метриками от Netflix. Это инструмент для управления данными многомерных временных рядов, который является идеальным местом для публикации собранных нами показателей.

Теперь мы продемонстрируем, как опубликовать наши показатели в Netflix Atlas.

Во-первых, давайте добавим зависимостьservo-atlas кpom.xml:


      com.netflix.servo
      servo-atlas
      ${netflix.servo.ver}



    0.12.17

Эта зависимость включает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

Чтобы сэкономить время для теста, давайте установим размер шага равным 1 секунде вmemory.conf, чтобы мы могли создать график временных рядов с достаточной детализацией показателей.

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);

После запускаPollScheduler с задачейPollRunnable мы можем автоматически публиковать метрики в 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"));

На основе показателей мы можем сгенерировать линейный график, используяgraph API Атласа:

image

5. Резюме

В этой статье мы познакомились с использованием Netflix Servo для сбора и публикации метрик приложений.

Если вы не читали наше введение в метрики Dropwizard, посмотритеhere для быстрого сравнения с Servo.

Как всегда, полный код реализации этой статьи можно найти вover on Github.