Einführung in Netflix Servo

1. Übersicht

Netflix Servo ist ein Metrik-Tool für Java-Anwendungen. Servo ähnelt Dropwizard Metrics, ist jedoch viel einfacher. JMX wird nur genutzt, um eine einfache Schnittstelle zum Anzeigen und Veröffentlichen von Anwendungsmetriken bereitzustellen.

In diesem Artikel stellen wir vor, was Servo bietet und wie wir damit Anwendungsmetriken erfassen und veröffentlichen können.

2. Maven-Abhängigkeiten

Bevor wir uns mit der eigentlichen Implementierung befassen, fügen wir der Datei pom.xml die Servo-Abhängigkeit hinzu :

 com.netflix.servo servo-core 0.12.16 

Außerdem sind viele Erweiterungen verfügbar, z. B. Servo-Apache, Servo-AWS usw. Wir benötigen sie möglicherweise später. Neueste Versionen dieser Erweiterungen finden Sie auch in Maven Central.

3. Sammeln Sie Metriken

Lassen Sie uns zunächst sehen, wie Sie Metriken aus unserer Anwendung erfassen.

Servo bietet vier primäre Metriktypen: Zähler , Messgerät , Timer und Information .

3.1. Metriktypen - Zähler

Zähler werden zum Aufzeichnen der Inkrementierung verwendet. Häufig verwendete Implementierungen sind BasicCounter , StepCounter und PeakRateCounter .

BasicCounter macht das, was ein Zähler tun sollte, einfach und unkompliziert:

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 gibt die maximale Anzahl für eine bestimmte Sekunde während des Abfrageintervalls zurück:

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

Im Gegensatz zu anderen Zählern zeichnet StepCounter die Rate pro Sekunde des vorherigen Abfrageintervalls auf:

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

Beachten Sie, dass wir die servo.pollers im obigen Code auf 1000 setzen . Das war, das Abfrageintervall auf 1 Sekunde anstatt auf Intervalle von 60 Sekunden und 10 Sekunden zu setzen. Wir werden später mehr darüber berichten.

3.2. Metrische Typen - Messgerät

Gauge ist ein einfacher Monitor, der den aktuellen Wert zurückgibt. BasicGauge , MinGauge , MaxGauge und NumberGauge werden bereitgestellt.

BasicGauge ruft ein Callable auf, um den aktuellen Wert abzurufen . Wir können die Größe einer Sammlung, den neuesten Wert einer BlockingQueue oder jeden Wert ermitteln, der kleine Berechnungen erfordert.

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

MaxGauge und MinGauge werden verwendet, um die Maximal- bzw. Minimalwerte zu verfolgen:

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 ) umschließt eine bereitgestellte Zahl ( Long , Double ). Um Metriken mit diesen Messgeräten zu erfassen, müssen wir sicherstellen, dass die Nummer threadsicher ist.

3.3. Metriktypen - Timer

Timer helfen beim Messen der Dauer eines bestimmten Ereignisses. Standardimplementierungen sind BasicTimer , StatsTimer und BucketTimer .

BasicTimer zeichnet Gesamtzeit, Anzahl und andere einfache Statistiken auf:

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 bietet viel umfangreichere Statistiken durch Stichproben zwischen den Abfrageintervallen :

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 bietet eine Möglichkeit, die Verteilung von Proben nach Bucketing-Wertebereichen zu ermitteln:

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

Um Langzeitvorgänge zu verfolgen, die Stunden dauern können, können wir den zusammengesetzten Monitor DurationTimer verwenden .

3.4. Metriktypen - Information

Außerdem können wir den Informationsmonitor verwenden , um beschreibende Informationen aufzuzeichnen, um das Debuggen und die Diagnose zu erleichtern . Die einzige Implementierung ist BasicInformational , und ihre Verwendung kann nicht einfacher sein:

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

3.5. MonitorRegistry

Die Metriktypen sind alle vom Typ Monitor , der die Basis von Servo bildet . Wir wissen jetzt, dass Arten von Tools Rohmetriken erfassen, aber um die Daten zu melden, müssen wir diese Monitore registrieren.

Beachten Sie, dass jeder einzelne konfigurierte Monitor einmal und nur einmal registriert werden sollte, um die Richtigkeit der Metriken sicherzustellen. So können wir die Monitore mit dem Singleton-Muster registrieren.

Meistens können wir DefaultMonitorRegistry verwenden , um Monitore zu registrieren:

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

Wenn wir einen Monitor dynamisch registrieren möchten , können DynamicTimer und DynamicCounter verwendet werden:

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

Beachten Sie, dass eine dynamische Registrierung bei jeder Aktualisierung des Werts zu teuren Suchvorgängen führen würde.

Servo bietet auch verschiedene Hilfsmethoden zum Registrieren von in Objekten deklarierten Monitoren:

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

Die Methode registerObject verwendet Reflection, um alle durch die Annotation @Monitor deklarierten Instanzen von Monitoren hinzuzufügen und von @MonitorTags deklarierte Tags hinzuzufügen :

@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. Veröffentlichen Sie Metriken

Mit den gesammelten Metriken können wir sie in jedem Format veröffentlichen, z. B. zum Rendern von Zeitreihendiagrammen auf verschiedenen Datenvisualisierungsplattformen. Um die Metriken zu veröffentlichen, müssen wir die Daten regelmäßig aus den Monitorbeobachtungen abfragen.

4.1. MetricPoller

MetricPoller wird als Metrikabruf verwendet. Wir können Metriken von MonitorRegistries , JVM, JMX abrufen . Mithilfe von Erweiterungen können wir Metriken wie den Apache-Serverstatus und Tomcat-Metriken abfragen.

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

Hier haben wir einen JvmMetricPoller erstellt, um Metriken von JVM abzufragen . Wenn Sie den Poller zum Scheduler hinzufügen, lassen wir die Poll-Task jede Sekunde laufen. Systemstandard-Poller-Konfigurationen sind in Pollers definiert , wir können jedoch Poller angeben, die mit der Systemeigenschaft servo.pollers verwendet werden sollen .

4.2. MetricObserver

Beim Abrufen von Metriken werden die Beobachtungen registrierter MetricObserver aktualisiert.

Standardmäßig bereitgestellte MetricObserver sind MemoryMetricObserver , FileMetricObserver und AsyncMetricObserver . Wir haben bereits im vorherigen Codebeispiel gezeigt, wie MemoryMetricObserver verwendet wird.

Derzeit sind mehrere nützliche Erweiterungen verfügbar:

  • AtlasMetricObserver : Veröffentlichen Sie Metriken in Netflix Atlas, um Zeitreihendaten für die Analyse im Speicher zu generieren
  • CloudWatchMetricObserver : Übertragen Sie Metriken zur Überwachung und Verfolgung von Metriken an Amazon CloudWatch
  • GraphiteObserver : Veröffentlichen Sie Metriken in Graphite, um sie zu speichern und grafisch darzustellen

Wir können einen angepassten MetricObserver implementieren , um Anwendungsmetriken dort zu veröffentlichen, wo wir es für richtig halten. Das einzige, was Sie beachten müssen, ist, mit den aktualisierten Metriken umzugehen:

public class CustomObserver extends BaseMetricObserver { //... @Override public void updateImpl(List metrics) { //TODO } }

4.3. In Netflix Atlas veröffentlichen

Atlas ist ein weiteres metrikbezogenes Tool von Netflix. Es ist ein Tool zum Verwalten von dimensionalen Zeitreihendaten. Hier können Sie die von uns gesammelten Metriken perfekt veröffentlichen.

Jetzt zeigen wir Ihnen, wie Sie unsere Metriken in Netflix Atlas veröffentlichen.

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

Basierend auf den Metriken können wir mithilfe der Diagramm-API von Atlas ein Liniendiagramm erstellen:

5. Zusammenfassung

In diesem Artikel haben wir die Verwendung von Netflix Servo zum Sammeln und Veröffentlichen von Anwendungsmetriken vorgestellt.

Falls Sie unsere Einführung in Dropwizard Metrics nicht gelesen haben, lesen Sie sie hier, um einen schnellen Vergleich mit Servo zu erhalten.

Wie immer finden Sie den vollständigen Implementierungscode dieses Artikels auf Github.