Panduan Cepat untuk Mikrometer

1. Perkenalan

Mikrometer menyediakan fasad sederhana di atas klien instrumentasi untuk sejumlah sistem pemantauan populer. Saat ini, mendukung sistem pemantauan berikut: Atlas, Datadog, Graphite, Ganglia, Influx, JMX dan Prometheus.

Pada artikel ini, kami akan memperkenalkan penggunaan dasar Mikrometer dan integrasinya dengan Spring.

Demi kesederhanaan, kami akan menggunakan Micrometer Atlas sebagai contoh untuk mendemonstrasikan sebagian besar kasus penggunaan kami.

2. Ketergantungan Maven

Untuk memulai, mari tambahkan ketergantungan berikut ke pom.xml :

 io.micrometer micrometer-registry-atlas 0.12.0.RELEASE 

Versi terbaru dapat ditemukan di sini.

3. MeterRegistry

Dalam Micrometer, MeterRegistry adalah komponen inti yang digunakan untuk mendaftarkan meter. Kami dapat melakukan iterasi pada registri dan selanjutnya metrik setiap meter, untuk menghasilkan deret waktu di backend dengan kombinasi metrik dan nilai dimensinya.

Bentuk paling sederhana dari registri adalah SimpleMeterRegistry . Namun dalam banyak kasus, kita harus menggunakan MeterRegistry yang dirancang secara eksplisit untuk sistem pemantauan kita; untuk Atlas, itu AtlasMeterRegistry .

CompositeMeterRegistry memungkinkan beberapa registri ditambahkan. Ini memberikan solusi untuk mempublikasikan metrik aplikasi ke berbagai sistem pemantauan yang didukung secara bersamaan.

Kami dapat menambahkan MeterRegistry apa pun yang diperlukan untuk mengunggah data ke berbagai platform:

CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry(); SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry(); AtlasMeterRegistry atlasMeterRegistry = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM); compositeRegistry.add(oneSimpleMeter); compositeRegistry.add(atlasMeterRegistry);

Ada dukungan registri global statis di Micrometer: Metrics.globalRegistry . Selain itu, sekumpulan pembuat statis berdasarkan registri global ini disediakan untuk menghasilkan pengukur dalam Metrik :

@Test public void givenGlobalRegistry_whenIncrementAnywhere_thenCounted() { class CountedObject { private CountedObject() { Metrics.counter("objects.instance").increment(1.0); } } Metrics.addRegistry(new SimpleMeterRegistry()); Metrics.counter("objects.instance").increment(); new CountedObject(); Optional counterOptional = Metrics.globalRegistry .find("objects.instance").counter(); assertTrue(counterOptional.isPresent()); assertTrue(counterOptional.get().count() == 2.0); }

4. Tag dan Meter

4.1. Tag

Pengenal Meteran terdiri dari nama dan tag. Disarankan agar kita mengikuti konvensi penamaan yang memisahkan kata dengan titik, untuk membantu menjamin portabilitas nama metrik di beberapa sistem pemantauan.

Counter counter = registry.counter("page.visitors", "age", "20s");

Tag dapat digunakan untuk memotong metrik untuk mempertimbangkan nilai. Pada kode di atas, page.visitors adalah nama dari meteran tersebut, dengan tag age = 20 - an . Dalam hal ini, penghitung dimaksudkan untuk menghitung pengunjung halaman dengan usia antara 20 dan 30.

Untuk sistem yang besar, kami dapat menambahkan tag umum ke registri, misalnya metrik tersebut berasal dari wilayah tertentu:

registry.config().commonTags("region", "ua-east");

4.2. Melawan

Sebuah Counter melaporkan hanya hitungan atas properti tertentu dari sebuah aplikasi. Kita dapat membuat penghitung khusus dengan fasih builder atau metode pembantu MetricRegistry mana pun :

Counter counter = Counter .builder("instance") .description("indicates instance count of the object") .tags("dev", "performance") .register(registry); counter.increment(2.0); assertTrue(counter.count() == 2); counter.increment(-1); assertTrue(counter.count() == 2);

Seperti yang terlihat dari potongan di atas, kami mencoba mengurangi penghitung satu per satu tetapi kami hanya dapat menambah penghitung secara monoton dengan jumlah positif tetap.

4.3. Timer

Untuk mengukur latensi atau frekuensi kejadian di sistem kami, kami dapat menggunakan Timer . Sebuah Timer akan melaporkan setidaknya total waktu dan peristiwa menghitung seri waktu tertentu.

Misalnya, kita bisa merekam event aplikasi yang mungkin berlangsung beberapa detik:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = registry.timer("app.event"); timer.record(() -> { try { TimeUnit.MILLISECONDS.sleep(1500); } catch (InterruptedException ignored) { } }); timer.record(3000, MILLISECONDS); assertTrue(2 == timer.count()); assertTrue(4510 > timer.totalTime(MILLISECONDS) && 4500 <= timer.totalTime(MILLISECONDS));

Untuk merekam acara yang berjalan lama, kami menggunakan LongTaskTimer :

SimpleMeterRegistry registry = new SimpleMeterRegistry(); LongTaskTimer longTaskTimer = LongTaskTimer .builder("3rdPartyService") .register(registry); long currentTaskId = longTaskTimer.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ignored) { } long timeElapsed = longTaskTimer.stop(currentTaskId); assertTrue(timeElapsed / (int) 1e9 == 2);

4.4. Mengukur

Alat pengukur menunjukkan nilai meter saat ini.

Berbeda dengan pengukur lainnya, Pengukur hanya melaporkan data saat diamati. Pengukur dapat berguna saat memantau statistik cache, koleksi, dll .:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); List list = new ArrayList(4); Gauge gauge = Gauge .builder("cache.size", list, List::size) .register(registry); assertTrue(gauge.value() == 0.0); list.add("1"); assertTrue(gauge.value() == 1.0);

4.5. DistributionSummary

Distribusi peristiwa dan ringkasan sederhana disediakan oleh DistributionSummary :

SimpleMeterRegistry registry = new SimpleMeterRegistry(); DistributionSummary distributionSummary = DistributionSummary .builder("request.size") .baseUnit("bytes") .register(registry); distributionSummary.record(3); distributionSummary.record(4); distributionSummary.record(5); assertTrue(3 == distributionSummary.count()); assertTrue(12 == distributionSummary.totalAmount());

Selain itu, DistributionSummary dan Timers dapat diperkaya dengan kuantil:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = Timer.builder("test.timer") .quantiles(WindowSketchQuantiles .quantiles(0.3, 0.5, 0.95) .create()) .register(registry);

Dalam potongan di atas, tiga pengukur dengan tag kuantil = 0,3 , kuantil = 0,5 dan kuantil = 0,95 akan tersedia di registri, yang menunjukkan nilai di bawah ini yang masing-masing memiliki 95%, 50% dan 30% pengamatan.

Untuk melihat kuantil ini beraksi, mari tambahkan record berikut:

timer.record(2, TimeUnit.SECONDS); timer.record(2, TimeUnit.SECONDS); timer.record(3, TimeUnit.SECONDS); timer.record(4, TimeUnit.SECONDS); timer.record(8, TimeUnit.SECONDS); timer.record(13, TimeUnit.SECONDS);

Kemudian kita dapat memverifikasi dengan mengekstraksi nilai dalam ketiga Pengukur Kuantil tersebut :

List quantileGauges = registry.getMeters().stream() .filter(m -> m.getType().name().equals("Gauge")) .map(meter -> (Gauge) meter) .collect(Collectors.toList()); assertTrue(3 == quantileGauges.size()); Map quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9); assertThat(quantileMap, allOf( hasEntry("quantile=0.3",2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8)));

Selain itu, Micrometer juga mendukung histogram:

DistributionSummary hist = DistributionSummary .builder("summary") .histogram(Histogram.linear(0, 10, 5)) .register(registry);

Mirip dengan kuantil, setelah menambahkan beberapa record, kita dapat melihat bahwa histogram menangani komputasi dengan cukup baik:

Map histograms = extractTagValueMap(registry, Type.Counter, 1.0); assertThat(histograms, allOf( hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0)));

Generally, histograms can help illustrate a direct comparison in separate buckets. Histograms can also be time scaled, which is quite useful for analyzing backend service response time:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = Timer .builder("timer") .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3)) .register(registry); //... assertThat(histograms, allOf( hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3)));

5. Binders

The Micrometer has multiple built-in binders to monitor the JVM, caches, ExecutorService and logging services.

When it comes to JVM and system monitoring, we can monitor class loader metrics (ClassLoaderMetrics), JVM memory pool (JvmMemoryMetrics) and GC metrics (JvmGcMetrics), thread and CPU utilization (JvmThreadMetrics, ProcessorMetrics).

Cache monitoring (currently, only Guava, EhCache, Hazelcast, and Caffeine are supported) is supported by instrumenting with GuavaCacheMetrics, EhCache2Metrics, HazelcastCacheMetrics, and CaffeineCacheMetrics. And to monitor log back service, we can bind LogbackMetrics to any valid registry:

new LogbackMetrics().bind(registry);

The usage of above binders are quite similar to LogbackMetrics and are all rather simple, so we won’t dive into further details here.

6. Spring Integration

Spring Boot Actuator provides dependency management and auto-configuration for Micrometer. Now it's supported in Spring Boot 2.0/1.x and Spring Framework 5.0/4.x.

We'll need the following dependency (the latest version can be found here):

 io.micrometer micrometer-spring-legacy 0.12.0.RELEASE 

Without any further change to existing code, we have enabled Spring support with the Micrometer. JVM memory metrics of our Spring application will be automatically registered in the global registry and published to the default atlas endpoint: //localhost:7101/api/v1/publish.

There're several configurable properties available to control metrics exporting behaviors, starting with spring.metrics.atlas.*. Check AtlasConfig to see a full list of configuration properties for Atlas publishing.

If we need to bind more metrics, only add them as @Bean to the application context.

Say we need the JvmThreadMetrics:

@Bean JvmThreadMetrics threadMetrics(){ return new JvmThreadMetrics(); }

As for web monitoring, it's auto-configured for every endpoint in our application, yet manageable via a configuration property: spring.metrics.web.autoTimeServerRequests.

The default implementation provides four dimensions of metrics for endpoints: HTTP request method, HTTP response code, endpoint URI, and exception information.

When requests are responded, metrics relating to request method (GET, POST, etc.) will be published in Atlas.

With Atlas Graph API, we can generate a graph to compare the response time for different methods:

By default, response codes of 20x, 30x, 40x, 50x will also be reported:

We can also compare different URIs :

or check exception metrics:

Note that we can also use @Timed on the controller class or specific endpoint methods to customize tags, long task, quantiles, and percentiles of the metrics:

@RestController @Timed("people") public class PeopleController { @GetMapping("/people") @Timed(value = "people.all", longTask = true) public List listPeople() { //... } }

Based on the code above, we can see the following tags by checking Atlas endpoint //localhost:7101/api/v1/tags/name:

["people", "people.all", "jvmBufferCount", ... ]

Micrometer also works in the function web framework introduced in Spring Boot 2.0. Metrics can be enabled by filtering the RouterFunction:

RouterFunctionMetrics metrics = new RouterFunctionMetrics(registry); RouterFunctions.route(...) .filter(metrics.timer("server.requests"));

Metrics from the data source and scheduled tasks can also be collected. Check the official documentation for more details.

7. Conclusion

Pada artikel ini, kami memperkenalkan fasad metrik Mikrometer. Dengan mengabstraksi dan mendukung beberapa sistem pemantauan di bawah semantik umum, alat ini membuat peralihan di antara platform pemantauan yang berbeda menjadi cukup mudah.

Seperti biasa, kode implementasi lengkap artikel ini dapat ditemukan di Github.