Въведение в Netflix Servo

1. Общ преглед

Netflix Servo е инструмент за показатели за Java приложения. Servo е подобен на Dropwizard Metrics, но много по-опростен. Той използва JMX само за предоставяне на опростен интерфейс за излагане и публикуване на показатели на приложенията.

В тази статия ще ви представим какво предлага Servo и как можем да го използваме за събиране и публикуване на показатели за приложения.

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

Преди да се потопим в реалното изпълнение, нека добавим зависимостта на Servo към файла pom.xml :

 com.netflix.servo servo-core 0.12.16 

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

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

Първо, нека видим как да съберем показатели от нашето приложение.

Servo предоставя четири основни типа показатели: брояч , измервателен уред , таймер и информационен .

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. Метрични типове - Габарит

Gauge е прост монитор, който връща текущата стойност. Осигурени са BasicGauge , MinGauge , MaxGauge и NumberGauges .

BasicGauge извиква изискуем за да получите текущата стойност. Можем да получим размера на колекция, последната стойност на 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 ) обгръща предоставен номер ( 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 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. Метрични типове - Информационни

Също така можем да използваме Информационния монитор, за да записваме описателна информация, за да помогнем при отстраняване на грешки и диагностика. Единствената реализация е 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");

Имайте предвид, че динамичната регистрация би довела до скъпа операция за търсене всеки път, когато стойността се актуализира.

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
    
      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 и показатели на 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
    
      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.

First, let's append the servo-atlas dependency to the pom.xml:

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

This dependency includes an AtlasMetricObserver to help us publish metrics to Atlas.

Then, we shall set up an Atlas server:

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

To save our time for the test, let's set the step size to 1 second in memory.conf, so that we can generate a time series graph with enough details of the metrics.

The AtlasMetricObserver requires a simple configuration and a list of tags. Metrics of the given tags will be pushed to Atlas:

System.setProperty("servo.pollers", "1000"); System.setProperty("servo.atlas.batchSize", "1"); System.setProperty("servo.atlas.uri", "//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);

After starting up a PollScheduler with the PollRunnable task, we can publish metrics to Atlas automatically:

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

Въз основа на показателите можем да генерираме линейна графика, използвайки графичен API на Atlas:

5. Обобщение

В тази статия представихме как да използваме Netflix Servo за събиране и публикуване на показатели за приложения.

В случай, че не сте прочели нашето въведение в Dropwizard Metrics, разгледайте го тук за бързо сравнение със Servo.

Както винаги, пълният код за изпълнение на тази статия може да бъде намерен в Github.