Перейти к основному содержимому

Введение в Netflix Servo

· 8 мин. чтения

1. Обзор

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

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

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

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

<dependency>
<groupId>com.netflix.servo</groupId>
<artifactId>servo-core</artifactId>
<version>0.12.16</version>
</dependency>

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

3. Собирайте метрики

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

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

3.1. Типы метрик — счетчик

Счетчики используются для записи приращения. Обычно используемые реализации: 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. Типы метрик – Датчик

Датчик — это простой монитор, который возвращает текущее значение. Предоставляются BasicGauge , MinGauge , MaxGauge и NumberGauge .

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 ) упаковывает предоставленный номер ( Long , Double ). Чтобы собирать метрики с помощью этих датчиков, мы должны убедиться, что число является потокобезопасным.

3.3. Типы метрик – Таймер

Таймеры помогают измерять продолжительность определенного события. Реализации по умолчанию: 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<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. Типы метрик — информационные

Кроме того, мы можем использовать информационный монитор для записи описательной информации, чтобы помочь в отладке и диагностике. Единственная реализация — BasicInformational , и ее использование не может быть проще:

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

3.5. МониторРеестр

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

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

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

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

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

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

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

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

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

Метод [registerObject](https://netflix.github.io/servo/current/servo-core/docs/javadoc/com/netflix/servo/monitor/Monitors.html#registerObject(java.lang.String, java.lang.Object)) будет использовать отражение для добавления всех экземпляров мониторов , объявленных аннотацией @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 .

`По умолчанию предоставляются MetricObserver : [MemoryMetricObserver](https://netflix.github.io/servo/current/servo-core/docs/javadoc/com/netflix/servo/publish/MemoryMetricObserver.html) , [FileMetricObserver](https://netflix.github.io/servo/current/servo-core/docs/javadoc/com/netflix/servo/publish/FileMetricObserver.html) и [AsyncMetricObserver](https://netflix.github.io/servo/current/servo-core/docs/javadoc/com/netflix/servo/publish/AsyncMetricObserver.html) . Мы уже показали, как использовать MemoryMetricObserver` в предыдущем примере кода.

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

  • AtlasMetricObserver : публикуйте метрики в Netflix Atlas для создания в памяти данных временных рядов для аналитики
  • CloudWatchMetricObserver : отправка метрик в Amazon CloudWatch для мониторинга и отслеживания метрик
  • GraphiteObserver : публиковать метрики в Graphite для хранения и отображения

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

public class CustomObserver extends BaseMetricObserver {

//...

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

4.3. Опубликовать в Атласе Netflix

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

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

Во-первых, давайте добавим зависимость 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

Чтобы сэкономить время для теста, давайте установим размер шага в 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 Atlas:

./77cdbfd0ce2861871451a10e533e7b85.png

5. Резюме

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

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

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