Micrometer為最流行的監控系統提供了一個簡單的儀表客戶端外觀,允許儀表化JVM應用,而無需關心是哪個供應商提供的指標。它的作用和SLF4J類似,只不過它關注的不是Logging(日誌),而是application metrics(應用指標)。簡而言之,它就是應用監控界的SLF4J。
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為例:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
2. 概念
2.1. Registry
Meter是收集關於你的應用的一系列指標的接口。Meter是由MeterRegistry創建的。每個支持的監控系統都必須實現MeterRegistry。
Micrometer中包含一個SimpleMeterRegistry,它在內存中維護每個meter的最新值,並且不將數據導出到任何地方。如果你還沒有一個首選的監測系統,你可以先用SimpleMeterRegistry:
MeterRegistry registry = new SimpleMeterRegistry();
注意:如果你用Spring的話,SimpleMeterRegistry是自動注入的
Micrometer還提供一個CompositeMeterRegistry用於將多個registries結合在一起使用,允許同時向多個監視系統發布指標。
CompositeMeterRegistry composite = new CompositeMeterRegistry();
Counter compositeCounter = composite.counter("counter");
compositeCounter.increment();
SimpleMeterRegistry simple = new SimpleMeterRegistry();
composite.add(simple);
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來覆蓋默認的命名轉換:
registry.config().namingConvention(myCustomNamingConvention);
有了命名約定以後,下面這個timer在不同的監控系統中看起來就是這樣的:
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請求數和資料庫調用次數,那麼可以這樣寫:
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等
registry.config().commonTags("stack", "prod", "region", "us-east-1");
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個方法:
實現MeterFilter就可以加到registry中
registry.config() .meterFilter(MeterFilter.ignoreTags("too.much.information")) .meterFilter(MeterFilter.denyNameStartsWith("jvm"));
過濾器按順序應用,所有的過濾器形成一個過濾器鏈(chain)
2.4.1. Deny/accept meters
接受或拒絕指標
new MeterFilter() {
@Override
public MeterFilterReply accept(Meter.Id id) { if(id.getName().contains("test")) {
return MeterFilterReply.DENY;
}
return MeterFilterReply.NEUTRAL;
}
}
MeterFilter還提供了許多方便的靜態方法用於接受或拒絕指標
2.4.2. Transforming metrics
一個轉換過濾器可能是這樣的:
new MeterFilter() {
@Override
public Meter.Id map(Meter.Id id) {
if(id.getName().startsWith("test")) {
return id.withName("extra." + id.getName()).withTag("extra.tag", "value");
}
return id;
}
}
2.5. Counters(計數器)
Counter接口允許以固定的數值遞增,該數值必須為正數。
2.6. Gauges
gauge是獲取當前值的句柄。典型的例子是,獲取集合、map、或運行中的線程數等。
MeterRegistry接口包含了用於構建gauges的方法,用於觀察數字值、函數、集合和map
2.7. Timers(計時器)
Timer用於測量短時間延遲和此類事件的頻率。所有Timer實現至少將總時間和事件次數報告為單獨的時間序列。
例如,可以考慮用一個圖表來顯示一個典型的web伺服器的請求延遲情況。伺服器可以快速響應許多請求,因此定時器每秒將更新很多次。
2.8. Long task timers
長任務計時器用於跟蹤所有正在運行的長時間運行任務的總持續時間和此類任務的數量。
Timer記錄的是次數,Long Task Timer記錄的是任務時長和任務數
2.9. Distribution summaries(分布匯總)
distribution summary用於跟蹤分布式的事件。它在結構上類似於計時器,但是記錄的值不代表時間單位。例如,記錄http伺服器上的請求的響應大小。
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的一個例子:
Timer.builder("my.timer").publishPercentiles(0.5, 0.95)
.publishPercentileHistogram()
.sla(Duration.ofMillis(100))
.minimumExpectedValue(Duration.ofMillis(1))
.maximumExpectedValue(Duration.ofSeconds(10))
3. Micrometer Prometheus
Prometheus是一個內存中的維度時間序列資料庫,具有簡單的內置UI、定製查詢語言和數學操作。Prometheus的設計是基於pull模型進行操作,根據服務發現定期從應用程式實例中抓取指標。
3.1. 安裝
1 <dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
3.2. 配置
Prometheus希望通過抓取或輪詢單個應用程式實例來獲得指標。除了創建Prometheus registry之外,還需要向Prometheus的scraper公開一個HTTP端點。在Spring環境中,一個Prometheus actuator endpoint是在Spring Boot Actuator存在的情況下自動配置的。
4. Spring Boot 2.0
Spring Boot Actuator提供依賴管理並自動配置Micrometer
實現meterBinder即可
package com.ata.channelManager.meter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 指標計時器 @micrometer
*
* @author jerry
*/
@Component
public class CounterMeter implements MeterBinder {
private AtomicInteger tokenCounter = new AtomicInteger();
private AtomicInteger smsCounter = new AtomicInteger();
private AtomicInteger bookCounter = new AtomicInteger();
private AtomicInteger httpErrorCounter = new AtomicInteger();
public CounterMeter() {
}
public int generateToken() {
return tokenCounter.incrementAndGet();
}
public int sms() {
return smsCounter.incrementAndGet();
}
public int book() {
return bookCounter.incrementAndGet();
}
public int errorHttp() {
return httpErrorCounter.incrementAndGet();
}
@Override
public void bindTo(MeterRegistry registry) {
Gauge.builder("chg5g.req.generateToken", () -> tokenCounter.get()).description("從網頁請求token的錯誤次數").tag("source", "web").register(registry);
Gauge.builder("chg5g.req.sms", () -> smsCounter.get()).description("從網頁請求簡訊驗證碼的錯誤次數").tag("source", "web").register(registry);
Gauge.builder("chg5g.req.book", () -> bookCounter.get()).description("從網頁請求下單的錯誤次數").tag("source", "web").register(registry);
Gauge.builder("chg5g.out.http.yidong", () -> httpErrorCounter.get()).description("http請求移動錯誤總次數").tag("out", "http").register(registry);
}
@Scheduled(cron = "0 * * * * ?")
private void reset() {
tokenCounter.set(0);
smsCounter.set(0);
bookCounter.set(0);
httpErrorCounter.set(0);
}
}