Pengenalan kepada Netflix Servo

1. Gambaran keseluruhan

Netflix Servo adalah alat metrik untuk aplikasi Java. Servo serupa dengan Metrik Dropwizard, tetapi lebih mudah. Ia memanfaatkan JMX hanya untuk menyediakan antara muka mudah untuk mendedahkan dan menerbitkan metrik aplikasi.

Dalam artikel ini, kami akan memperkenalkan apa yang disediakan oleh Servo dan bagaimana kami dapat menggunakannya untuk mengumpulkan dan menerbitkan metrik aplikasi.

2. Pergantungan Maven

Sebelum kita menyelidiki pelaksanaan sebenarnya, mari tambahkan kebergantungan Servo ke fail pom.xml :

 com.netflix.servo servo-core 0.12.16 

Selain itu, terdapat banyak sambungan yang tersedia, seperti Servo-Apache, Servo-AWS, dll. Kita mungkin memerlukannya kemudian. Versi terbaru dari sambungan ini juga boleh didapati di Maven Central.

3. Kumpulkan Sukatan

Pertama, mari kita lihat bagaimana mengumpulkan metrik dari aplikasi kita.

Servo menyediakan empat jenis metrik utama: Kaunter , Tolok , Pemasa, dan Maklumat .

3.1. Jenis Metrik - Pembilang

Pembilang digunakan untuk mencatat kenaikan. Pelaksanaan yang biasa digunakan adalah BasicCounter , StepCounter , dan PeakRateCounter .

BasicCounter melakukan apa yang harus dilakukan oleh kaunter, sederhana dan mudah:

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 mengembalikan kiraan maksimum untuk satu detik selama selang pengundian:

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

Tidak seperti kaunter lain, StepCounter mencatatkan kadar per saat selang undian sebelumnya:

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

Perhatikan bahawa kita menetapkan servo.pollers menjadi 1000 dalam kod di atas. Itu untuk menetapkan selang pengundian menjadi 1 saat dan bukannya selang 60 saat dan 10 saat secara lalai. Kami akan membahas lebih lanjut mengenai perkara ini kemudian.

3.2. Jenis Metrik - Tolok

Gauge adalah monitor sederhana yang mengembalikan nilai semasa. BasicGauge , MinGauge , MaxGauge , dan NumberGauges disediakan.

BasicGauge menyembah yang Dipanggil untuk mendapatkan nilai semasa. Kita boleh mendapatkan ukuran koleksi, nilai terbaru dari BlockingQueue atau nilai apa pun yang memerlukan pengiraan kecil.

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

MaxGauge dan MinGauge digunakan untuk memantau nilai maksimum dan minimum masing-masing:

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 ) membungkus Nombor ( Long , Double ) yang disediakan. Untuk mengumpul metrik dengan menggunakan tolok ini, kita perlu memastikan Nombor adalah thread selamat.

3.3. Jenis Metrik - Pemasa

Pemasa membantu mengukur jangka masa peristiwa tertentu. Pelaksanaan lalai adalah BasicTimer , StatsTimer , dan BucketTimer .

BasicTimer mencatatkan jumlah masa, kiraan dan statistik mudah lain:

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 memberikan statistik yang lebih kaya dengan mengambil sampel antara selang undian:

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 menyediakan cara untuk mendapatkan pengedaran sampel dengan julat nilai baldi:

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

Untuk mengesan operasi jangka panjang yang mungkin berlangsung berjam-jam, kita dapat menggunakan komposit monitor DurationTimer .

3.4. Jenis Metrik - Maklumat

Kami juga dapat menggunakan monitor Maklumat untuk merakam maklumat deskriptif untuk membantu proses penyahpepijatan dan diagnostik. Satu-satunya pelaksanaan adalah BasicInformational , dan penggunaannya tidak boleh lebih sederhana:

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

3.5. MonitorRegistry

Jenis metrik adalah semua jenis Monitor , yang merupakan asas Servo . Kami sekarang tahu jenis alat mengumpulkan metrik mentah, tetapi untuk melaporkan data, kami perlu mendaftarkan monitor ini.

Perhatikan bahawa setiap monitor yang dikonfigurasi harus didaftarkan sekali dan hanya sekali untuk memastikan kebenaran metrik. Oleh itu, kita boleh mendaftarkan monitor menggunakan corak Singleton.

Selalunya, kita boleh menggunakan DefaultMonitorRegistry untuk mendaftarkan monitor:

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

Sekiranya kita ingin mendaftarkan monitor secara dinamis , DynamicTimer , dan DynamicCounter dapat digunakan:

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

Perhatikan bahawa pendaftaran dinamik akan menyebabkan operasi pencarian mahal setiap kali nilainya dikemas kini.

Servo juga menyediakan beberapa kaedah pembantu untuk mendaftarkan monitor yang dinyatakan dalam objek:

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

Method registerObject will use reflection to add all instances of Monitors declared by annotation @Monitor and add tags declared by @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. Publish Metrics

With the metrics collected, we can publish it to in any format, such as rendering time series graphs on various data visualization platforms. To publish the metrics, we need to poll the data periodically from the monitor observations.

4.1. MetricPoller

MetricPoller is used as a metrics fetcher. We can fetch metrics of MonitorRegistries, JVM, JMX. With the help of extensions, we can poll metrics like Apache server status and Tomcat 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"));
    

Here we created a JvmMetricPoller to poll metrics of JVM. When adding the poller to the scheduler, we let the poll task to run every second. System default poller configurations are defined in Pollers, but we can specify pollers to use with system property servo.pollers.

4.2. MetricObserver

When polling metrics, observations of registered MetricObservers will be updated.

MetricObservers provided by default are MemoryMetricObserver, FileMetricObserver, and AsyncMetricObserver. We have already shown how to use MemoryMetricObserver in the previous code sample.

Currently, several useful extensions are available:

  • AtlasMetricObserver: publish metrics to Netflix Atlas to generate in memory time series data for analytics
  • CloudWatchMetricObserver: push metrics to Amazon CloudWatch for metrics monitoring and tracking
  • GraphiteObserver: publish metrics to Graphite to store and graph

We can implement a customized MetricObserver to publish application metrics to where we see fit. The only thing to care about is to handle the updated metrics:

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

4.3. Publish to Netflix Atlas

Atlas is another metrics-related tool from Netflix. It's a tool for managing dimensional time series data, which is a perfect place to publish the metrics we collected.

Now, we'll demonstrate how to publish our metrics to 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"));

Berdasarkan metrik, kita dapat menghasilkan grafik garis menggunakan grafik API Atlas:

5. Ringkasan

Dalam artikel ini, kami telah memperkenalkan cara menggunakan Netflix Servo untuk mengumpulkan dan menerbitkan metrik aplikasi.

Sekiranya anda belum membaca pengenalan kami untuk Metrik Dropwizard, lihat di sini untuk perbandingan pantas dengan Servo.

Seperti biasa, kod pelaksanaan lengkap artikel ini terdapat di Github.