Micrometer 快速入門


Micrometer為最流行的監控系統提供了一個簡單的儀表客戶端外觀,允許儀表化JVM應用,而無需關心是哪個供應商提供的指標。它的作用和SLF4J類似,只不過它關注的不是Logging(日志),而是application metrics(應用指標)。簡而言之,它就是應用監控界的SLF4J。

Micrometer(譯:千分尺)

不妨看看SLF4J官網上對於SLF4J的說明:Simple Logging Facade for Java (SLF4J)

現在再看Micrometer的說明:Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems.

 

Metrics(譯:指標,度量)

 

Micrometer提供了與供應商無關的接口,包括 timers(計時器)gauges(量規)counters(計數器)distribution summaries(分布式摘要)long task timers(長任務定時器)。它具有維度數據模型,當與維度監視系統結合使用時,可以高效地訪問特定的命名度量,並能夠跨維度深入研究。

支持的監控系統:AppOptics , Azure Monitor , Netflix Atlas , CloudWatch , Datadog , Dynatrace , Elastic , Ganglia , Graphite , Humio , Influx/Telegraf , JMX , KairosDB , New Relic , Prometheus , SignalFx , Google Stackdriver , StatsD , Wavefront

 

1.  安裝

Micrometer記錄的應用程序指標用於觀察、告警和對環境當前/最近的操作狀態做出反應。

為了使用Micrometer,首先要添加你所選擇的監視系統的依賴。以Prometheus為例:

1 <dependency>
2 	<groupId>io.micrometer</groupId>
3   	<artifactId>micrometer-registry-prometheus</artifactId>
4   	<version>${micrometer.version}</version>
5 </dependency>

 

2.  概念

2.1.  Registry

Meter是收集關於你的應用的一系列指標的接口。Meter是由MeterRegistry創建的。每個支持的監控系統都必須實現MeterRegistry。 

Micrometer中包含一個SimpleMeterRegistry,它在內存中維護每個meter的最新值,並且不將數據導出到任何地方。如果你還沒有一個首選的監測系統,你可以先用SimpleMeterRegistry: 

1 MeterRegistry registry = new SimpleMeterRegistry(); 

注意:如果你用Spring的話,SimpleMeterRegistry是自動注入的 

Micrometer還提供一個CompositeMeterRegistry用於將多個registries結合在一起使用,允許同時向多個監視系統發布指標。 

1 CompositeMeterRegistry composite = new CompositeMeterRegistry();
2 
3 Counter compositeCounter = composite.counter("counter");
4 compositeCounter.increment();
5 
6 SimpleMeterRegistry simple = new SimpleMeterRegistry();
7 composite.add(simple);
8 
9 compositeCounter.increment();

 

2.2.  Meters

Micrometer提供一系列原生的Meter,包括Timer , Counter , Gauge , DistributionSummary , LongTaskTimer , FunctionCounter , FunctionTimer , TimeGauge。不同的meter類型導致有不同的時間序列指標值。例如,單個指標值用Gauge表示,計時事件的次數和總時間用Timer表示。

每一項指標都有一個唯一標識的名字和維度。“維度”和“標簽”是一個意思,Micrometer中有一個Tag接口,僅僅因為它更簡短。一般來說,應該盡可能地使用名稱作為軸心。

(PS:指標的名字很好理解,維度怎么理解呢?如果把name想象成橫坐標的話,那么dimension就是縱坐標。Tag是一個key/value對,代表指標的一個維度值) 

 

2.3.  Naming meters(指標命名)

Micrometer使用了一種命名約定,用.分隔小寫單詞字符。不同的監控系統有不同的命名約定。每個Micrometer的實現都要負責將Micrometer這種以.分隔的小寫字符命名轉換成對應監控系統推薦的命名。你可以提供一個自己的NamingConvention來覆蓋默認的命名轉換:

1 registry.config().namingConvention(myCustomNamingConvention); 

 有了命名約定以后,下面這個timer在不同的監控系統中看起來就是這樣的:

1 registry.timer("http.server.requests"); 

在Prometheus中,它是http_server_requests_duration_seconds

在Atlas中,它對應的是httpServerRequests

在InfluxDB中,對應的是http_server_requests

(PS:每項指標都有一個名字,不同的監控系統的命名規則(風格)都不太一樣,因此可能同一個指標在不同的監控系統中有不同的名字。簡單地來說,比如內存使用率這個指標可能在Prometheus中用MemoryUsage表示,在InfluxDB中用mem_usage表示,因此每個監控系統都要提供一個命名轉換器,當看到mem.usage的時候InfluxDB應該知道說的是內存使用率,對應的指標名稱是mem_usage。這就好比,中文“你好”翻譯成英文是“hello”,翻譯成日文是“こんにちは” ) 

2.3.1.  Tag naming

假設,我們想要統計HTTP請求數和數據庫調用次數,那么可以這樣寫:

1 registry.counter("database.calls", "db", "users");       // 數據庫調用次數
2 registry.counter("http.requests", "uri", "/api/users");  // HTTP請求數

2.3.2.  Common tags

Common tags可以被定義在registry級別,並且會被添加到每個監控系統的報告中

預定義的Tags有host , instance , region , stack等 

1 registry.config().commonTags("stack", "prod", "region", "us-east-1");
2 registry.config().commonTags(Arrays.asList(Tag.of("stack", "prod"), Tag.of("region", "us-east-1"))); // 二者等價

2.3.4.  Tag values

Tag values must be non-null 

 

2.4.  Meter filters

每個registry都可以配置指標過濾器,它有3個方法:

Deny (or accept) meters from being registered

Transform meter IDs

Configure distribution statistics for some meter types.

實現MeterFilter就可以加到registry中

1 registry.config()
2     .meterFilter(MeterFilter.ignoreTags("too.much.information"))
3     .meterFilter(MeterFilter.denyNameStartsWith("jvm")); 

過濾器按順序應用,所有的過濾器形成一個過濾器鏈(chain) 

2.4.1.  Deny/accept meters

接受或拒絕指標

1 new MeterFilter() {
2     @Override
3     public MeterFilterReply accept(Meter.Id id) {
4        if(id.getName().contains("test")) {
5           return MeterFilterReply.DENY;
6        }
7        return MeterFilterReply.NEUTRAL;
8     }
9 }

MeterFilter還提供了許多方便的靜態方法用於接受或拒絕指標 

2.4.2.  Transforming metrics 

一個轉換過濾器可能是這樣的:

1 new MeterFilter() {
2     @Override
3     public Meter.Id map(Meter.Id id) {
4        if(id.getName().startsWith("test")) {
5           return id.withName("extra." + id.getName()).withTag("extra.tag", "value");
6        }
7        return id;
8     }
9 }

 

2.5.  Counters(計數器)

Counter接口允許以固定的數值遞增,該數值必須為正數。 

 1 MeterRegistry registry = new SimpleMeterRegistry();
 2 
 3 // 寫法一
 4 Counter counter = registry.counter("counter");
 5 
 6 // 寫法二
 7 Counter counter = Counter
 8     .builder("counter")
 9     .baseUnit("beans") // optional
10     .description("a description of what this counter does") // optional
11     .tags("region", "test") // optional
12     .register(registry);

2.5.1.  Function-tracking counters

跟蹤單調遞增函數的計數器

1 Cache cache = ...; // suppose we have a Guava cache with stats recording on
2 registry.more().counter("evictions", tags, cache, c -> c.stats().evictionCount()); // evictionCount()是一個單調遞增函數,用於記錄緩存被剔除的次數

 

2.6.  Gauges

gauge是獲取當前值的句柄。典型的例子是,獲取集合、map、或運行中的線程數等。

MeterRegistry接口包含了用於構建gauges的方法,用於觀察數字值、函數、集合和map。 

1 List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size); //監視非數值對象
2 List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>()); //監視集合大小
3 Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>()); 

還可以手動加減Gauge

1 AtomicInteger n = registry.gauge("numberGauge", new AtomicInteger(0));
2 n.set(1);
3 n.set(2);

 

2.7.  Timers(計時器) 

Timer用於測量短時間延遲和此類事件的頻率。所有Timer實現至少將總時間和事件次數報告為單獨的時間序列。

例如,可以考慮用一個圖表來顯示一個典型的web服務器的請求延遲情況。服務器可以快速響應許多請求,因此定時器每秒將更新很多次。

 1 // 方式一
 2 public interface Timer extends Meter {
 3     ...
 4     void record(long amount, TimeUnit unit);
 5     void record(Duration duration);
 6     double totalTime(TimeUnit unit);
 7 }
 8 
 9 // 方式二
10 Timer timer = Timer
11     .builder("my.timer")
12     .description("a description of what this timer does") // optional
13     .tags("region", "test") // optional
14     .register(registry); 

查看源代碼,一目了然,不一一贅述

 

 

2.8.  Long task timers 

長任務計時器用於跟蹤所有正在運行的長時間運行任務的總持續時間和此類任務的數量。 

Timer記錄的是次數,Long Task Timer記錄的是任務時長和任務數

 1 // 方式一
 2 @Timed(value = "aws.scrape", longTask = true)
 3 @Scheduled(fixedDelay = 360000)
 4 void scrapeResources() {
 5     // find instances, volumes, auto-scaling groups, etc...
 6 }
 7 
 8 // 方式二
 9 LongTaskTimer scrapeTimer = registry.more().longTaskTimer("scrape");
10 void scrapeResources() {
11     scrapeTimer.record(() => {
12         // find instances, volumes, auto-scaling groups, etc...
13     });
14 }

 

2.9.  Distribution summaries(分布匯總)

distribution summary用於跟蹤分布式的事件。它在結構上類似於計時器,但是記錄的值不代表時間單位。例如,記錄http服務器上的請求的響應大小。

1 DistributionSummary summary = registry.summary("response.size");

 

2.10.  Histograms and percentiles(直方圖和百分比) 

Timers 和 distribution summaries 支持收集數據來觀察它們的百分比。查看百分比有兩種主要方法:

Percentile histograms(百分比直方圖):  Micrometer將值累積到底層直方圖,並將一組預先確定的buckets發送到監控系統。監控系統的查詢語言負責從這個直方圖中計算百分比。目前,只有Prometheus , Atlas , Wavefront支持基於直方圖的百分位數近似值,並且通過histogram_quantile , :percentile , hs()依次表示。

Client-side percentiles(客戶端百分比):Micrometer為每個meter ID(一組name和tag)計算百分位數近似值,並將百分位數值發送到監控系統。

下面是用直方圖構建Timer的一個例子:

1 Timer.builder("my.timer")
2    .publishPercentiles(0.5, 0.95) // median and 95th percentile
3    .publishPercentileHistogram()
4    .sla(Duration.ofMillis(100))
5    .minimumExpectedValue(Duration.ofMillis(1))
6    .maximumExpectedValue(Duration.ofSeconds(10))

 

3.  Micrometer Prometheus 

Prometheus是一個內存中的維度時間序列數據庫,具有簡單的內置UI、定制查詢語言和數學操作。Prometheus的設計是基於pull模型進行操作,根據服務發現定期從應用程序實例中抓取指標。

 

3.1.  安裝

1 <dependency>
2     <groupId>io.micrometer</groupId>
3     <artifactId>micrometer-registry-prometheus</artifactId>
4     <version>${micrometer.version}</version>
5 </dependency>

 

3.2.  配置

Prometheus希望通過抓取或輪詢單個應用程序實例來獲得指標。除了創建Prometheus registry之外,還需要向Prometheus的scraper公開一個HTTP端點。在Spring環境中,一個Prometheus actuator endpoint是在Spring Boot Actuator存在的情況下自動配置的。

下面的示例使用JDK的com.sun.net.httpserver.HttpServer來公布scrape端點: 

 1 PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
 2 
 3 try {
 4     HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
 5     server.createContext("/prometheus", httpExchange -> {
 6         String response = prometheusRegistry.scrape();
 7         httpExchange.sendResponseHeaders(200, response.getBytes().length);
 8         try (OutputStream os = httpExchange.getResponseBody()) {
 9             os.write(response.getBytes());
10         }
11     });
12 
13     new Thread(server::start).start();
14 } catch (IOException e) {
15     throw new RuntimeException(e);
16 }

 

3.3.  圖表

Grafana Dashboard

 

4.  Spring Boot 2.0

Spring Boot Actuator提供依賴管理並自動配置Micrometer

Spring Boot 自動配置一個組合的MeterRegistry,並添加一個registry到這個組合MeterRegistry中。

你可以注冊任意數量的MeterRegistryCustomizer來進一步配置registry

1 @Bean
2 MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
3     return registry -> registry.config().commonTags("region", "us-east-1");
4 }

你可以在組件中注入MeterRegistry,並注冊指標:

 1 @Component
 2 public class SampleBean {
 3 
 4     private final Counter counter;
 5 
 6     public SampleBean(MeterRegistry registry) {
 7         this.counter = registry.counter("received.messages");
 8     }
 9 
10     public void handleMessage(String message) {
11         this.counter.increment();
12         // handle message implementation
13     }
14 
15 }

Spring Boot為Prometheus提供/actuator/prometheus端點

下面是一個簡單的例子,scrape_config添加到prometheus.yml中:

1 scrape_configs:
2   - job_name: 'spring'
3     metrics_path: '/actuator/prometheus'
4     static_configs:
5       - targets: ['HOST:PORT']

 

5.  JVM、Cache、OkHttpClient

Micrometer提供了幾個用於監視JVM、Cache等的binder。例如:

 1 new ClassLoaderMetrics().bindTo(registry);
 2 new JvmMemoryMetrics().bindTo(registry);
 3 new JvmGcMetrics().bindTo(registry);
 4 new ProcessorMetrics().bindTo(registry);
 5 new JvmThreadMetrics().bindTo(registry);
 6 
 7 // 通過添加OkHttpMetricsEventListener來收集OkHttpClient指標
 8 OkHttpClient client = new OkHttpClient.Builder()
 9     .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
10         .tags(Tags.of("foo", "bar"))
11         .build())
12     .build();
13 // 為了配置URI mapper,可以用uriMapper()
14 OkHttpClient client = new OkHttpClient.Builder()
15     .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
16         .uriMapper(req -> req.url().encodedPath())
17         .tags(Tags.of("foo", "bar"))
18         .build())
19     .build(); 

還有很多內置的Binder,看圖:

 

最后,切記文檔是用來查的,此處只是一個引子,有個大概印象,等需要用的時候再細查文檔即可。

 

6.  文檔

http://micrometer.io

http://micrometer.io/docs 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM