spring-boot整合Micrometer+Prometheus的詳細過程
環(huán)境:
micrometer 1.8.2
prometheus 0.14.1
spring-boot-actuator 2.6.6
使用案例
<!-- Springboot啟動actuator,默認會引入依賴:micrometer-core --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>2.6.6</version> </dependency> <!-- micrometer橋接prometheus包,默認會引入相關依賴:io.prometheus:simpleclient --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>1.8.2</version> </dependency>
Timer
打點記錄任務的每次執(zhí)行時間。兜底默認的時間窗口是1分鐘。如果想要修改可以配置:io.micrometer.core.instrument.distribution.DistributionStatisticConfig.Builder#expiry
Metrics.timer("my_name", "my_tag_1", "my_tag_2").record(() -> {
doMyJob();
});LongTaskTimer
與Timer類似,記錄任務執(zhí)行時間,官方注釋中也說了LongTask是一個主觀判斷,比如:1分鐘以上的任務
一個比較大區(qū)別在于多了一個接口方法:io.micrometer.core.instrument.LongTaskTimer#activeTasks
獲取當前正在執(zhí)行中的任務數(shù)量???????
Metrics.more().longTaskTimer("my_name", "my_tag").record(doMyJob());Gague
在服務器拉取指標時,或者客戶端上報指標時,調用提供的對象與方法獲取當前指標。即:記錄的是當前狀態(tài)
RingBuffer<MatchingOutput> rb = disruptor.getRingBuffer();
Metrics.gauge("ringbuffer_remaining", Tags.of("my_tag_1", "my_tag_2"), rb, RingBuffer::remainingCapacity);Counter
計數(shù)器打點
Metrics.counter("my_request", "my_tag_1", "my_tag_2").increment();DistributionSummary
跟蹤事件的樣本分布。 一個例子是訪問 http 服務器的請求的響應大小。???????
DistributionSummary ds = DistributionSummary.builder("my.data.size")
.tag("type", "my_type_1")
.publishPercentileHistogram()
.register(Metrics.globalRegistry);
ds.record(myValue);配置actuator
配置指標拉取端口,以及需要曝光的web接口???????
management:
server:
port: 9999
endpoints:
web:
exposure:
include: '*'
metrics:
tags:
application: myAppNameSpringboot整合啟動流程
拉取指標:http://localhost:9999/actuator/prometheus
servlet配置
接口自動配置有很多入口,例如下面兩個
- 普通web服務:org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration#webEndpointServletHandlerMapping
- 云服務商:org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration#cloudFoundryWebEndpointServletHandlerMapping
servlet邏輯
org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint
@ReadOperation(producesFrom = TextOutputFormat.class)
public WebEndpointResponse<String> scrape(TextOutputFormat format, @Nullable Set<String> includedNames) {
try {
Writer writer = new StringWriter(this.nextMetricsScrapeSize);
Enumeration<MetricFamilySamples> samples = (includedNames != null)
? this.collectorRegistry.filteredMetricFamilySamples(includedNames)
: this.collectorRegistry.metricFamilySamples();
format.write(writer, samples);
String scrapePage = writer.toString();
this.nextMetricsScrapeSize = scrapePage.length() + METRICS_SCRAPE_CHARS_EXTRA;
return new WebEndpointResponse<>(scrapePage, format);
}
catch (IOException ex) {
// This actually never happens since StringWriter doesn't throw an IOException
throw new IllegalStateException("Writing metrics failed", ex);
}
}沒有配置過濾器,獲取枚舉對象
io.prometheus.client.CollectorRegistry#metricFamilySamples -》 io.prometheus.client.CollectorRegistry.MetricFamilySamplesEnumeration#MetricFamilySamplesEnumeration()
io.prometheus.client.CollectorRegistry.MetricFamilySamplesEnumeration
- sampleNameFilter
- collectorIter:對應io.prometheus.client.CollectorRegistry#namesToCollectors屬性的value集合
- 構造器中查詢一次next:findNextElement
findNextElement
遍歷collectorIter迭代器一次,并收集一次指標
- io.prometheus.client.Collector#collect(io.prometheus.client.Predicate<java.lang.String>)
- io.micrometer.prometheus.MicrometerCollector#collect
- 遍歷io.micrometer.prometheus.MicrometerCollector#children集合中所有io.micrometer.prometheus.MicrometerCollector.Child對象
- 例如Gauge類型中的lambda匿名實現(xiàn):io.micrometer.prometheus.PrometheusMeterRegistry#newGauge
- 將遍歷的child中所有樣本,按照conventionName(例如:ringbuffer_remaining)分組,每個組對應一個樣本家庭:io.prometheus.client.Collector.MetricFamilySamples
- 返回List,將其迭代器賦給next屬性:io.prometheus.client.CollectorRegistry.MetricFamilySamplesEnumeration#next
- 遍歷samples:io.prometheus.client.Collector.MetricFamilySamples#samples
- 將sample(io.prometheus.client.Collector.MetricFamilySamples.Sample)數(shù)據(jù)寫入response響應結果:org.springframework.boot.actuate.metrics.export.prometheus.TextOutputFormat#CONTENT_TYPE_004#write
接口輸出案例
公共配置的tag,所有的指標都會帶有該tag:application=myAppName
指標名稱:ringbuffer_remaining
指標tag:type=my_tag_1
指標類型:gauge
# HELP ringbuffer_remaining
# TYPE ringbuffer_remaining gauge
ringbuffer_remaining{application="myAppName",type="my_tag_1",} 1024.0采樣取數(shù)邏輯
Gauge
結合前面Gague使用案例的代碼
io.micrometer.core.instrument.internal.DefaultGauge
- ref:對應ringbuffer實例的弱引用
- value:對應RingBuffer::remainingCapacity方法
取樣邏輯即直接調用實例響應方法返回的結果作為打點value
public class DefaultGauge<T> extends AbstractMeter implements Gauge {
...
private final WeakReference<T> ref;
private final ToDoubleFunction<T> value;
...
@Override
public double value() {
T obj = ref.get();
if (obj != null) {
try {
return value.applyAsDouble(obj);
}
catch (Throwable ex) {
logger.log("Failed to apply the value function for the gauge '" + getId().getName() + "'.", ex);
}
}
return Double.NaN;
}
...
}Timer
io.micrometer.prometheus.PrometheusTimer
- count:LongAdder,遞增計數(shù)器
- totalTime:LongAdder,任務耗時累加結果
- max:io.micrometer.core.instrument.distribution.TimeWindowMax,簡化版的ringbuffer,用于記錄時間窗口中的最大值
- histogramFlavor:直方圖風味(類型),當前版本只有兩種:Prometheus/VictoriaMetrics
- histogram
- Prometheus類型:io.micrometer.core.instrument.distribution.TimeWindowFixedBoundaryHistogram#TimeWindowFixedBoundaryHistogram
- VictoriaMetrics類型:io.micrometer.core.instrument.distribution.FixedBoundaryVictoriaMetricsHistogram#FixedBoundaryVictoriaMetricsHistogram
取樣邏輯即監(jiān)控的方法實際調用時就會觸發(fā)打點記錄。取樣邏輯只是在接口拉取數(shù)據(jù)時調用實例實現(xiàn)的接口方法拍一個樣本快照
- io.micrometer.core.instrument.distribution.HistogramSupport#takeSnapshot()
- io.micrometer.prometheus.PrometheusTimer#takeSnapshot
- io.micrometer.core.instrument.AbstractTimer#takeSnapshot
- 如果histogram != null則追加histogramCounts數(shù)據(jù)
--io.micrometer.core.instrument.AbstractTimer#takeSnapshot
@Override
public HistogramSnapshot takeSnapshot() {
return histogram.takeSnapshot(count(), totalTime(TimeUnit.NANOSECONDS), max(TimeUnit.NANOSECONDS));
}
--io.micrometer.prometheus.PrometheusTimer#takeSnapshot
@Override
public HistogramSnapshot takeSnapshot() {
HistogramSnapshot snapshot = super.takeSnapshot();
if (histogram == null) {
return snapshot;
}
return new HistogramSnapshot(snapshot.count(),
snapshot.total(),
snapshot.max(),
snapshot.percentileValues(),
histogramCounts(),
snapshot::outputSummary);
}時間窗口
io.micrometer.core.instrument.distribution.TimeWindowMax
- rotatingUpdater:AtomicIntegerFieldUpdater,rotating標志符原子更新方法
- clock:Clock,系統(tǒng)時鐘,返回當前系統(tǒng)時間戳
- durationBetweenRotatesMills:long,滾動步進大小
- ringBuffer:AtomicLong[],隊列
- currentBucket:int,隊列當前游標
- lastRotateTimestampMills:上一次rotate的時間戳
- rotating:int,標志符,0 - not rotating, 1 - rotating
每次寫入record,或者查詢poll,都會提前校驗下是否需要翻轉,調用rotate方法
io.micrometer.core.instrument.distribution.TimeWindowMax#rotate
- wallTime=系統(tǒng)當前時間
- timeSinceLastRotateMillis = wallTime - lastRotateTimestampMillis,即:當前時間距離上次翻轉的時間間隔
- 如果低于步進,直接返回不需要翻轉:timeSinceLastRotateMillis < durationBetweenRotatesMillis
- 否則更新標志符,表示當前正在翻轉,需要阻塞等待下
- 如果timeSinceLastRotateMillis已經超出整個隊列的長度了:timeSinceLastRotateMillis >= durationBetweenRotatesMillis * ringBuffer.length
- 那么直接重置隊列返回即可
- 遍歷ringBuffer所有位置設置為0
- currentBucket更新為0
- 更新上次翻轉時間:lastRotateTimestampMillis = wallTime - timeSinceLastRotateMillis % durationBetweenRotatesMillis
- 否則,將當前時間與上次翻轉時間之間已經超時的bucket重置為0
int iterations = 0;
do {
ringBuffer[currentBucket].set(0);
if (++currentBucket >= ringBuffer.length) {
currentBucket = 0;
}
timeSinceLastRotateMillis -= durationBetweenRotatesMillis;
lastRotateTimestampMillis += durationBetweenRotatesMillis;
} while (timeSinceLastRotateMillis >= durationBetweenRotatesMillis && ++iterations < ringBuffer.length);例如:當前時間為4,上次翻轉時間為2,隊列大小為3,durationBetweenRotatesMillis=1,currentBucket=1,那么timeSinceLastRotateMillis=4-2=2
循環(huán)第1輪
- 更新ringBuffer[1]=0
- 更新currentBucket=2
- 更新timeSinceLastRotateMillis=2-1
- 更新lastRotateTimestampMillis=2+1
- 更新iterations=1
循環(huán)第2輪
- 更新ringBuffer[2]=0
- 更新currentBucket=3
- currentBucket>=隊列長度
- 重置currentBucket=0
- 更新timeSinceLastRotateMillis=1-1
- 更新lastRotateTimestampMillis=3+1
- 更新iterations=2,此時timeSinceLastRotateMillis=0,小于durationBetweenRotatesMillis,結束循環(huán)
一次旋轉圖例
當發(fā)現(xiàn)上次旋轉時間(lastRotateTimestampMillis)已經落后當前時間(wallTime)4個單位后,lastRotateTimestampMillis向右移動4個時間單位,currentBucket也向右移動4個單位。但是因為currentBucket是數(shù)組的index,當越界的時候就移動到0繼續(xù)(一個環(huán))。例如下圖:
currentBucket向右移動4個單位,隊列長度為3,當前index=0,那么移動后index=2(轉了一圈)
總結
Micrometer可以整合Prometheus也可以整合influxDB等時序數(shù)據(jù)庫,主要作用就是橋接,類似于Slf4j與log4j,logback的關系。提供一個通用的打點能力,并將打點數(shù)據(jù)對接到相應的時序數(shù)據(jù)庫,用戶只需要關心何時打點即可。例如:
- 橋接包中的io.micrometer.prometheus.PrometheusMeterRegistry,將打點數(shù)據(jù)橋接至io.prometheus.client.CollectorRegistry
- 橋接包中的io.micrometer.influx.InfluxMeterRegistry,將打點數(shù)據(jù)按照influx協(xié)議橋接push至influxDB。
- 默認push頻率為1分鐘一次,可以按需配置:io.micrometer.core.instrument.push.PushRegistryConfig#step
- 線程池默認為單線程:java.util.concurrent.Executors#newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory)
- 線程池線程命名規(guī)則針對influxDB實現(xiàn):influx-metrics-publisher
actuator就像是啟動器,會將對接具體時序數(shù)據(jù)庫所需要的配置自動化,例如指標矩陣相關的:Prometheus曝光web接口的相關配置,influx相關配置,micrometer metrics等等相關配置
- org.springframework.boot.actuate.autoconfigure.metrics.JvmMetricsAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.metrics.KafkaMetricsAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.metrics.Log4J2MetricsAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.metrics.LogbackMetricsAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration
最終可以通過Grafana等報表工具對接打點數(shù)據(jù)源展示圖表。常見的架構有:Micrometer-》Prometheus-〉Grafana
注意:前端頁面渲染存在瓶頸,例如一個指標的tag如果太多會導致報表非常的卡頓,一般5k個tag就會有感知,1W+會明顯影響使用
到此這篇關于spring-boot整合Micrometer+Prometheus的文章就介紹到這了,更多相關spring-boot整合Micrometer+Prometheus內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring?Cloud?Alibaba使用Nacos作為注冊中心和配置中心
這篇文章主要為大家介紹了Spring?Cloud?Alibaba使用Nacos作為注冊中心和配置中心的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
SpringBoot連接PostgreSQL+MybatisPlus入門案例(代碼詳解)
這篇文章主要介紹了SpringBoot連接PostgreSQL+MybatisPlus入門案例,本文通過實例代碼圖文相結合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-07-07
Spring Boot Admin(監(jiān)控工具)的使用
今天我們將會講解一個優(yōu)秀的監(jiān)控工具Spring Boot Admin。 它采用圖形化的界面,讓我們的Spring Boot管理更加簡單,需要的朋友可以參考下2020-02-02
使用IDEA直接連接MySQL數(shù)據(jù)庫的方法
這篇文章主要介紹了如何使用IDEA直接連接MySQL數(shù)據(jù)庫,首先需要新建一個空項目,第一次連接 需要先下載驅動,文中給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-04-04

