Micrometer 快速入門

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

Micrometer(譯:千分尺)spring

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

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

 

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 , Wavefrontapp

 

1.  安裝jvm

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

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

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 

相關文章
相關標籤/搜索