Introduction à Netflix Servo

Introduction à Netflix Servo

1. Vue d'ensemble

Netflix Servo est un outil de métrique pour les applications Java. Servo est similaire àDropwizard Metrics, mais beaucoup plus simple. Il utilise JMX uniquement pour fournir une interface simple permettant d'afficher et de publier des métriques d'application.

Dans cet article, nous présenterons ce que propose Servo et comment pouvons-nous l'utiliser pour collecter et publier des métriques d'application.

2. Dépendances Maven

Avant de plonger dans l'implémentation réelle, ajoutons la dépendanceServo au fichierpom.xml:


    com.netflix.servo
    servo-core
    0.12.16

En outre, il existe de nombreuses extensions disponibles, telles queServo-Apache,Servo-AWS, etc. Nous aurons peut-être besoin d'eux plus tard. Les dernières versions de ces extensions peuvent également être trouvées surMaven Central.

3. Collecter des métriques

Voyons d'abord comment collecter des métriques à partir de notre application.

Servo fournit quatre types de métriques principaux:Counter,Gauge,Timer, etInformational.

3.1. Types de métriques - Compteur

Counters sont utilisés pour enregistrer l'incrémentation. Les implémentations couramment utilisées sontBasicCounter,StepCounter etPeakRateCounter.

BasicCounter fait ce qu'un compteur doit faire, clairement et simplement:

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 renvoie le nombre maximum pour une seconde donnée pendant l'intervalle d'interrogation:

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

Contrairement aux autres compteurs,StepCounter enregistre le taux par seconde de l'intervalle d'interrogation précédent:

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

Notez que nous définissons lesservo.pollers sur1000 dans le code ci-dessus. Cela consistait à définir l'intervalle d'interrogation sur 1 seconde au lieu d'intervalles de 60 secondes et 10 secondes par défaut. Nous en parlerons plus tard plus tard.

3.2. Types de métriques - Jauge

Gauge est un moniteur simple qui renvoie la valeur actuelle. BasicGauge,MinGauge,MaxGauge etNumberGauges sont fournis.

BasicGauge invoque unCallable pour obtenir la valeur actuelle. Nous pouvons obtenir la taille d'une collection, la dernière valeur d'unBlockingQueue ou toute valeur qui nécessite de petits calculs.

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

assertEquals(2.32, gauge.getValue(), 0.01);

MaxGauge etMinGauge sont utilisés pour garder une trace des valeurs maximum et minimum respectivement:

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) encapsule unNumber (Long,Double) fourni. Pour collecter des métriques à l'aide de ces jauges, nous devons nous assurer que lesNumber sont thread-safe.

3.3. Types de métriques - Minuteur

Timers aide à mesurer la durée d'un événement particulier. Les implémentations par défaut sontBasicTimer,StatsTimer etBucketTimer.

BasicTimer enregistre le temps total, le nombre et d'autres statistiques simples:

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 fournit des statistiques beaucoup plus riches en échantillonnant entre les intervalles d'interrogation:

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 fournit un moyen d'obtenir la distribution des échantillons en regroupant des plages de valeurs:

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

Pour suivre les opérations de longue durée qui peuvent durer des heures, nous pouvons utiliser le moniteur compositeDurationTimer.

3.4. Types de métriques - Informatif

De plus, nous pouvons utiliser le moniteurInformational pour enregistrer des informations descriptives afin de faciliter le débogage et les diagnostics. La seule implémentation estBasicInformational, et son utilisation ne peut pas être plus simple:

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

3.5. MonitorRegistry

Les types de métriques sont tous de typeMonitor, qui est la base même deServo. Nous savons maintenant que les types d’outils collectent des mesures brutes, mais pour rapporter les données, nous devons enregistrer ces moniteurs.

Notez que chaque moniteur configuré doit être enregistré une et une seule fois pour garantir l'exactitude des métriques. Ainsi, nous pouvons enregistrer les moniteurs en utilisant le modèle Singleton.

La plupart du temps, nous pouvons utiliserDefaultMonitorRegistry pour enregistrer les moniteurs:

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

Si nous voulons enregistrer dynamiquement un moniteur,DynamicTimer etDynamicCounter peuvent être utilisés:

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

Notez que l'enregistrement dynamique entraînerait une opération de recherche coûteuse chaque fois que la valeur est mise à jour.

Servo fournit également plusieurs méthodes d'assistance pour enregistrer les moniteurs déclarés dans les objets:

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

La méthoderegisterObject utilisera la réflexion pour ajouter toutes les instances deMonitors déclarées par l'annotation@Monitor et ajouter les balises déclarées par@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. Publier des métriques

Avec les métriques collectées, nous pouvons le publier dans n'importe quel format, tel que le rendu des graphiques de séries temporelles sur diverses plateformes de visualisation de données. Pour publier les métriques, nous devons interroger les données périodiquement à partir des observations du moniteur.

4.1. MetricPoller

MetricPoller est utilisé comme récupérateur de métriques. Nous pouvons récupérer les métriques deMonitorRegistries,JVM,JMX. Avec l'aide d'extensions, nous pouvons interroger des métriques telles queApache server status etTomcat 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"));

Ici, nous avons créé unJvmMetricPoller pour interroger les métriques de JVM. Lors de l'ajout du poller au planificateur, nous laissons la tâche d'interrogation s'exécuter toutes les secondes. Les configurations d'interrogation par défaut du système sont définies dansPollers, mais nous pouvons spécifier des pollers à utiliser avec la propriété systèmeservo.pollers.

4.2. MetricObserver

Lors de l'interrogation des métriques, les observations desMetricObserversenregistrés seront mises à jour.

LesMetricObservers fournis par défaut sontMemoryMetricObserver,FileMetricObserver etAsyncMetricObserver. Nous avons déjà montré comment utiliserMemoryMetricObserver dans l'exemple de code précédent.

Actuellement, plusieurs extensions utiles sont disponibles:

Nous pouvons implémenter unMetricObserver personnalisé pour publier les métriques d'application là où nous le souhaitons. La seule chose à prendre en compte est de gérer les métriques mises à jour:

public class CustomObserver extends BaseMetricObserver {

    //...

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

4.3. Publier sur Netflix Atlas

Atlas est un autre outil lié aux métriques de Netflix. Il s’agit d’un outil de gestion des données de séries chronologiques dimensionnelles, l’endroit idéal pour publier les métriques que nous avons collectées.

Nous allons maintenant vous montrer comment publier nos métriques sur Netflix Atlas.

Tout d'abord, ajoutons la dépendanceservo-atlas auxpom.xml:


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



    0.12.17

Cette dépendance inclut unAtlasMetricObserver pour nous aider à publier des métriques surAtlas.

Ensuite, nous allons mettre en place un serveur 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

Pour gagner du temps pour le test, définissons la taille du pas sur 1 seconde enmemory.conf, afin que nous puissions générer un graphique chronologique avec suffisamment de détails sur les métriques.

LeAtlasMetricObserver nécessite une configuration simple et une liste de balises. Les métriques des tags donnés seront poussées vers 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);

Après avoir démarré unPollScheduler avec la tâchePollRunnable, nous pouvons publier automatiquement des métriques sur 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"));

Sur la base des métriques, nous pouvons générer un graphique linéaire en utilisantgraph API d'Atlas:

image

5. Sommaire

Dans cet article, nous avons expliqué comment utiliser Netflix Servo pour collecter et publier des métriques d'application.

Si vous n’avez pas lu notre introduction à Dropwizard Metrics, consultezhere pour une comparaison rapide avec Servo.

Comme toujours, le code d'implémentation complet de cet article se trouveover on Github.