Einführung in Netflix Servo

Einführung in Netflix Servo

1. Überblick

Netflix Servo ist ein Metrik-Tool für Java-Anwendungen. Servo ähneltDropwizard Metrics, ist jedoch viel einfacher. Es nutzt JMX nur, 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 derpom.xml-Datei die Abhängigkeit vonServohinzu:


    com.netflix.servo
    servo-core
    0.12.16

Außerdem sind viele Erweiterungen verfügbar, z. B.Servo-Apache,Servo-AWS usw. Wir brauchen sie vielleicht später. Neueste Versionen dieser Erweiterungen finden Sie auch aufMaven Central.

3. Metriken sammeln

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

Servo bietet vier primäre Metriktypen:Counter,Gauge,Timer, undInformational.

3.1. Metriktypen - Zähler

Counters werden zum Aufzeichnen der Inkrementierung verwendet. Häufig verwendete Implementierungen sindBasicCounter,StepCounter undPeakRateCounter.

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 zeichnetStepCounterdie Aufzeichnungsrate 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 im obigen Codeservo.pollers auf1000 setzen. Damit sollte das Abfrageintervall auf 1 Sekunde anstatt standardmäßig auf 60 Sekunden und 10 Sekunden eingestellt werden. Wir werden später mehr darüber berichten.

3.2. Metriktypen - Spur

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

BasicGauge ruftCallable auf, um den aktuellen Wert zu erhalten. Wir können die Größe einer Sammlung, den neuesten Wert vonBlockingQueue 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 undMinGauge 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 ein bereitgestelltesNumber (Long,Double). Um Metriken mit diesen Messgeräten zu erfassen, müssen wir sicherstellen, dassNumber threadsicher ist.

3.3. Metriktypen - Timer

Timers helfen dabei, die Dauer eines bestimmten Ereignisses zu messen. Standardimplementierungen sindBasicTimer,StatsTimer undBucketTimer.

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 liefert 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 Stichproben durch Bucketing-Wertebereiche 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 MonitorDurationTimer verwenden.

3.4. Metriktypen - Informativ

Wir können auch denInformational-Monitor verwenden, um beschreibende Informationen aufzuzeichnen, um das Debuggen und die Diagnose zu erleichtern. Die einzige Implementierung istBasicInformational, 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 TypMonitor, was die Basis vonServo ist. Wir wissen jetzt, dass verschiedene Tools Rohdaten erfassen. Um die Daten zu melden, müssen diese Monitore registriert werden.

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

Meistens können wirDefaultMonitorRegistry 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önnenDynamicTimer undDynamicCounter 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 Monitoren, die in Objekten deklariert sind:

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

MethoderegisterObject verwendet Reflektion, um alle durch Annotation@Monitor deklarierten Instanzen vonMonitors und durch@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. Metriken veröffentlichen

Mit den gesammelten Metriken können wir sie in einem beliebigen Format veröffentlichen, beispielsweise zum Rendern von Zeitreihendiagrammen auf verschiedenen Datenvisualisierungsplattformen. Um die Metriken zu veröffentlichen, müssen die Daten regelmäßig von den Monitorbeobachtungen abgefragt werden.

4.1. MetricPoller

MetricPoller wird als Metrikabruf verwendet. Wir können Metriken vonMonitorRegistries,JVM,JMX abrufen. Mit Hilfe von Erweiterungen können wir Metriken wieApache server status undTomcat metrics 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 einJvmMetricPoller erstellt, um Metriken von JVM abzufragen. Wenn Sie den Poller zum Scheduler hinzufügen, wird der Polling-Task jede Sekunde ausgeführt. Systemstandard-Poller-Konfigurationen werden inPollers definiert, wir können jedoch Poller angeben, die mit der Systemeigenschaftservo.pollers verwendet werden sollen.

4.2. MetricObserver

Bei der Abfrage von Metriken werden die Beobachtungen der registriertenMetricObservers aktualisiert.

MetricObservers sind standardmäßigMemoryMetricObserver,FileMetricObserver undAsyncMetricObserver. Wir haben bereits im vorherigen Codebeispiel gezeigt, wieMemoryMetricObserver verwendet werden.

Derzeit sind verschiedene nützliche Erweiterungen verfügbar:

Wir können ein benutzerdefiniertesMetricObserver implementieren, um Anwendungsmetriken dort zu veröffentlichen, wo wir es für richtig halten. Das einzige, was Sie beachten müssen, ist, die aktualisierten Messdaten zu verarbeiten:

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.

Fügen wir zunächst die Abhängigkeit vonservo-atlasanpom.xmlan:


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



    0.12.17

Diese Abhängigkeit enthältAtlasMetricObserver, damit wir Metriken inAtlas veröffentlichen können.

Dann richten wir einen Atlas-Server ein:

$ 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

Um Zeit für den Test zu sparen, setzen wir die Schrittgröße inmemory.conf auf 1 Sekunde, damit wir ein Zeitreihendiagramm mit genügend Details der Metriken erstellen können.

DasAtlasMetricObserver erfordert eine einfache Konfiguration und eine Liste von Tags. Metriken der angegebenen Tags werden an Atlas gesendet:

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

Nach dem Start vonPollScheduler mit der AufgabePollRunnable können wir Metriken automatisch in Atlas veröffentlichen:

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 mitgraph API von Atlas ein Liniendiagramm erstellen:

image

5. Zusammenfassung

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

Falls Sie unsere Einführung in Dropwizard-Metriken nicht gelesen haben, lesen Siehere, um einen schnellen Vergleich mit Servo zu erhalten.

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