Introduction au servo Netflix

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 pour exposer et publier les métriques d'application.

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

2. Dépendances de Maven

Avant de plonger dans l'implémentation réelle, ajoutons la dépendance Servo au fichier pom.xml :

 com.netflix.servo servo-core 0.12.16 

En outre, il existe de nombreuses extensions disponibles, telles que Servo-Apache, Servo-AWS, etc. Nous pourrions en avoir besoin plus tard. Les dernières versions de ces extensions sont également disponibles sur Maven Central.

3. Collectez des métriques

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

Servo fournit quatre types de métriques principaux: compteur , jauge , minuterie et informationnel .

3.1. Types métriques - Compteur

Les compteurs sont utilisés pour enregistrer l'incrémentation. Les implémentations couramment utilisées sont BasicCounter , StepCounter et PeakRateCounter .

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 maximal 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 les servo.pollers sur 1000 dans le code ci-dessus. Cela consistait à définir l'intervalle d'interrogation à 1 seconde au lieu d'intervalles de 60 secondes et 10 secondes par défaut. Nous en parlerons plus tard.

3.2. Types métriques - Jauge

La jauge est un moniteur simple qui renvoie la valeur actuelle. BasicGauge , MinGauge , MaxGauge et NumberGauges sont fournis.

BasicGauge appelle un Callable pour obtenir la valeur actuelle. Nous pouvons obtenir la taille d'une collection, la dernière valeur d'un BlockingQueue ou toute valeur nécessitant de petits calculs.

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

MaxGauge et MinGauge sont utilisés pour garder une trace des valeurs maximale et minimale 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 un nombre fourni ( Long , Double ). Pour collecter des métriques à l'aide de ces jauges, nous devons nous assurer que le nombre est thread-safe.

3.3. Types métriques - Minuterie

Les minuteries aident à mesurer la durée d'un événement particulier. Les implémentations par défaut sont BasicTimer , StatsTimer et BucketTimer .

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 composite DurationTimer .

3.4. Types de métriques - Informatif

En outre, nous pouvons utiliser le moniteur d' information pour enregistrer des informations descriptives afin de faciliter le débogage et les diagnostics. La seule implémentation est BasicInformational , et son utilisation ne peut pas être plus simple:

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

3.5. MoniteurRegistry

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

Notez que chaque moniteur configuré unique doit être enregistré une et une seule fois pour garantir l'exactitude des mesures. Nous pouvons donc enregistrer les moniteurs en utilisant le modèle Singleton.

La plupart du temps, nous pouvons utiliser DefaultMonitorRegistry pour enregistrer des moniteurs:

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

Si nous voulons enregistrer dynamiquement un moniteur, DynamicTimer et DynamicCounter 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éthode registerObject utilisera la réflexion pour ajouter toutes les instances de moniteurs déclarées par l'annotation @Monitor et ajouter des 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 les publier dans n'importe quel format, tel que le rendu de graphiques de séries chronologiques sur diverses plates-formes de visualisation de données. Pour publier les métriques, nous devons interroger périodiquement les données à partir des observations du moniteur.

4.1. MetricPoller

MetricPoller est utilisé comme un récupérateur de métriques. Nous pouvons récupérer les métriques de MonitorRegistries , JVM, JMX. À l'aide d'extensions, nous pouvons interroger des métriques telles que l'état du serveur Apache et les métriques 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"));
    

Ici, nous avons créé un JvmMetricPoller 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 dans Pollers , mais nous pouvons spécifier des pollers à utiliser avec les servo-collecteurs de propriété système .

4.2. MetricObserver

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

Les MetricObservers fournis par défaut sont MemoryMetricObserver , FileMetricObserver et AsyncMetricObserver . Nous avons déjà montré comment utiliser MemoryMetricObserver dans l'exemple de code précédent.

Actuellement, plusieurs extensions utiles sont disponibles:

  • AtlasMetricObserver : publier des métriques sur Netflix Atlas pour générer en mémoire des données de séries chronologiques à des fins d'analyse
  • CloudWatchMetricObserver : transmettez les métriques à Amazon CloudWatch pour la surveillance et le suivi des métriques
  • GraphiteObserver : publier des métriques sur Graphite pour stocker et représenter graphiquement

Nous pouvons implémenter un MetricObserver personnalisé pour publier les métriques d'application là où nous le souhaitons. La seule chose dont il faut se soucier 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. C'est un outil de gestion des données de séries chronologiques dimensionnelles, qui est un endroit idéal pour publier les métriques que nous avons collectées.

Nous allons maintenant montrer comment publier nos métriques sur 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"));

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

5. Résumé

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

Si vous n'avez pas lu notre introduction à Dropwizard Metrics, consultez-la ici pour une comparaison rapide avec Servo.

Comme toujours, le code d'implémentation complet de cet article est disponible sur Github.