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包,默認會引入相關(guān)依賴:io.prometheus:simpleclient --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>1.8.2</version> </dependency>
Timer
打點記錄任務(wù)的每次執(zhí)行時間。兜底默認的時間窗口是1分鐘。如果想要修改可以配置:io.micrometer.core.instrument.distribution.DistributionStatisticConfig.Builder#expiry
Metrics.timer("my_name", "my_tag_1", "my_tag_2").record(() -> { doMyJob(); });
LongTaskTimer
與Timer類似,記錄任務(wù)執(zhí)行時間,官方注釋中也說了LongTask是一個主觀判斷,比如:1分鐘以上的任務(wù)
一個比較大區(qū)別在于多了一個接口方法:io.micrometer.core.instrument.LongTaskTimer#activeTasks
獲取當(dāng)前正在執(zhí)行中的任務(wù)數(shù)量???????
Metrics.more().longTaskTimer("my_name", "my_tag").record(doMyJob());
Gague
在服務(wù)器拉取指標時,或者客戶端上報指標時,調(diào)用提供的對象與方法獲取當(dāng)前指標。即:記錄的是當(dāng)前狀態(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 服務(wù)器的請求的響應(yīng)大小。???????
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: myAppName
Springboot整合啟動流程
拉取指標:http://localhost:9999/actuator/prometheus
servlet配置
接口自動配置有很多入口,例如下面兩個
- 普通web服務(wù):org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration#webEndpointServletHandlerMapping
- 云服務(wù)商: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:對應(yīng)io.prometheus.client.CollectorRegistry#namesToCollectors屬性的value集合
- 構(gòu)造器中查詢一次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)分組,每個組對應(yīng)一個樣本家庭: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響應(yīng)結(jié)果: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
結(jié)合前面Gague使用案例的代碼
io.micrometer.core.instrument.internal.DefaultGauge
- ref:對應(yīng)ringbuffer實例的弱引用
- value:對應(yīng)RingBuffer::remainingCapacity方法
取樣邏輯即直接調(diào)用實例響應(yīng)方法返回的結(jié)果作為打點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,任務(wù)耗時累加結(jié)果
- max:io.micrometer.core.instrument.distribution.TimeWindowMax,簡化版的ringbuffer,用于記錄時間窗口中的最大值
- histogramFlavor:直方圖風(fēng)味(類型),當(dāng)前版本只有兩種:Prometheus/VictoriaMetrics
- histogram
- Prometheus類型:io.micrometer.core.instrument.distribution.TimeWindowFixedBoundaryHistogram#TimeWindowFixedBoundaryHistogram
- VictoriaMetrics類型:io.micrometer.core.instrument.distribution.FixedBoundaryVictoriaMetricsHistogram#FixedBoundaryVictoriaMetricsHistogram
取樣邏輯即監(jiān)控的方法實際調(diào)用時就會觸發(fā)打點記錄。取樣邏輯只是在接口拉取數(shù)據(jù)時調(diào)用實例實現(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)時鐘,返回當(dāng)前系統(tǒng)時間戳
- durationBetweenRotatesMills:long,滾動步進大小
- ringBuffer:AtomicLong[],隊列
- currentBucket:int,隊列當(dāng)前游標
- lastRotateTimestampMills:上一次rotate的時間戳
- rotating:int,標志符,0 - not rotating, 1 - rotating
每次寫入record,或者查詢poll,都會提前校驗下是否需要翻轉(zhuǎn),調(diào)用rotate方法
io.micrometer.core.instrument.distribution.TimeWindowMax#rotate
- wallTime=系統(tǒng)當(dāng)前時間
- timeSinceLastRotateMillis = wallTime - lastRotateTimestampMillis,即:當(dāng)前時間距離上次翻轉(zhuǎn)的時間間隔
- 如果低于步進,直接返回不需要翻轉(zhuǎn):timeSinceLastRotateMillis < durationBetweenRotatesMillis
- 否則更新標志符,表示當(dāng)前正在翻轉(zhuǎn),需要阻塞等待下
- 如果timeSinceLastRotateMillis已經(jīng)超出整個隊列的長度了:timeSinceLastRotateMillis >= durationBetweenRotatesMillis * ringBuffer.length
- 那么直接重置隊列返回即可
- 遍歷ringBuffer所有位置設(shè)置為0
- currentBucket更新為0
- 更新上次翻轉(zhuǎn)時間:lastRotateTimestampMillis = wallTime - timeSinceLastRotateMillis % durationBetweenRotatesMillis
- 否則,將當(dāng)前時間與上次翻轉(zhuǎn)時間之間已經(jīng)超時的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);
例如:當(dāng)前時間為4,上次翻轉(zhuǎn)時間為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,結(jié)束循環(huán)
一次旋轉(zhuǎn)圖例
當(dāng)發(fā)現(xiàn)上次旋轉(zhuǎn)時間(lastRotateTimestampMillis)已經(jīng)落后當(dāng)前時間(wallTime)4個單位后,lastRotateTimestampMillis向右移動4個時間單位,currentBucket也向右移動4個單位。但是因為currentBucket是數(shù)組的index,當(dāng)越界的時候就移動到0繼續(xù)(一個環(huán))。例如下圖:
currentBucket向右移動4個單位,隊列長度為3,當(dāng)前index=0,那么移動后index=2(轉(zhuǎn)了一圈)
總結(jié)
Micrometer可以整合Prometheus也可以整合influxDB等時序數(shù)據(jù)庫,主要作用就是橋接,類似于Slf4j與log4j,logback的關(guān)系。提供一個通用的打點能力,并將打點數(shù)據(jù)對接到相應(yīng)的時序數(shù)據(jù)庫,用戶只需要關(guān)心何時打點即可。例如:
- 橋接包中的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ù)庫所需要的配置自動化,例如指標矩陣相關(guān)的:Prometheus曝光web接口的相關(guān)配置,influx相關(guān)配置,micrometer metrics等等相關(guān)配置
- 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ù)源展示圖表。常見的架構(gòu)有:Micrometer-》Prometheus-〉Grafana
注意:前端頁面渲染存在瓶頸,例如一個指標的tag如果太多會導(dǎo)致報表非常的卡頓,一般5k個tag就會有感知,1W+會明顯影響使用
到此這篇關(guān)于spring-boot整合Micrometer+Prometheus的文章就介紹到這了,更多相關(guān)spring-boot整合Micrometer+Prometheus內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Cloud?Alibaba使用Nacos作為注冊中心和配置中心
這篇文章主要為大家介紹了Spring?Cloud?Alibaba使用Nacos作為注冊中心和配置中心的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06SpringBoot連接PostgreSQL+MybatisPlus入門案例(代碼詳解)
這篇文章主要介紹了SpringBoot連接PostgreSQL+MybatisPlus入門案例,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-07-07Spring Boot Admin(監(jiān)控工具)的使用
今天我們將會講解一個優(yōu)秀的監(jiān)控工具Spring Boot Admin。 它采用圖形化的界面,讓我們的Spring Boot管理更加簡單,需要的朋友可以參考下2020-02-02使用IDEA直接連接MySQL數(shù)據(jù)庫的方法
這篇文章主要介紹了如何使用IDEA直接連接MySQL數(shù)據(jù)庫,首先需要新建一個空項目,第一次連接 需要先下載驅(qū)動,文中給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-04-04