如何在 Prometheus 中解析千分尺上的 NaN 值
1. 概述
微米級度量表是一種便捷的方式,可以將記憶體物件的狀態作為應用程式指標公開。但是,如果配置不正確,它們可能會產生意外結果,例如傳回NaN而不是實際值。這會影響指標在 Prometheus 或任何其他指標後端中的顯示方式。
在本教程中,我們將重現一個常見場景:由於垃圾回收,計量器會報告NaN 。然後,我們將討論其根本原因,並透過維護對被監控對象的強引用來修復此問題。
2. 重現NaN問題
假設我們要監控一個 Java 物件的狀態,並透過 Spring Boot Actuator 將其公開。
首先,讓我們在專案中加入spring-boot-starter-actuator依賴項,這也會引入 Micrometer 依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
現在,我們可以將MeterRegistry注入到服務類別中,並使用它來註冊一個觀察物件的Gauge 。例如,我們可以透過監聽ApplicationReadyEvent ,在 Spring 應用程式完全初始化時建立並註冊Gauge :
@Service
class FooService {
private final MeterRegistry registry;
// constructor
@EventListener(ApplicationReadyEvent.class)
public void setupGauges() {
setupWeakReferenceGauge();
}
private void setupWeakReferenceGauge() {
Foo foo = new Foo(10);
Gauge.builder("foo.weak", foo, Foo::value)
.description("Foo value - weak reference (will show NaN after GC)")
.register(registry);
}
record Foo(int value) {
}
}
如果我們現在驗證foo.weak指標,我們可能會看到值為 10。但是,如果我們本地啟動應用程式並對/actuator/metrics/foo.weak執行 GET 請求,我們可能會立即或最終看到值為NaN :
{
"name": "foo.weak",
"description": "Foo value - weak reference (will show NaN after GC)",
"measurements": [
{
"statistic": "VALUE",
"value": "NaN"
}
],
"availableTags": []
}
因此,我們從儀表中得到的數據是無用的。
3. 了解根本原因
要了解Gauge為何返回NaN ,我們需要查看兩件事:
- Java 垃圾回收如何處理不同的參考類型
- 千分尺如何實現測量
在 Java 中,我們可以透過不同的方式引用物件。強引用是常見的引用類型,只要物件存在,它就不會被垃圾回收。相反,弱引用則不會阻止垃圾回收。當 JVM 需要記憶體時,它可以回收那些僅透過弱引用存取的物件。
Micrometer 的Gauge的設計旨在避免記憶體洩漏。因此,它預設不會對被監控對象保持強引用。相反, Gauge會儲存弱引用,並在抓取指標時呼叫提供的值函數。
在上面的範例中, Foo實例是在setupWeakReferenceGauge方法內部建立的,並且沒有儲存在其他任何地方。此方法執行完畢後,該物件不再有任何強引用。這意味著Foo實例幾乎立即符合垃圾回收的條件。當垃圾回收器移除該物件時,儀表所持有的弱引用也會被清除。之後,當執行器端點嘗試讀取指標值時, Micrometer 將無法再存取該對象,因此它將該值報告為NaN 。
4. 使用強而有力的參考文獻
為了防止計量器回傳NaN ,我們需要確保被監控的物件不會被垃圾回收。最簡單的方法是保持對該物件的強引用。
例如,我們可以將Foo儲存為服務類別中的一個欄位。另一方面,我們可以保持程式碼不變,直接利用Gauge.Builder API中的.strongReference(true) :
private void setupStrongReferenceGauge() {
Foo foo = new Foo(10);
Gauge.builder("foo.strong", foo, Foo::value)
.description("Foo value - strong reference (will persist)")
.strongReference(true)
.register(registry);
}
好了!讓我們使用路徑/actuator/metrics/foo.strong來驗證新儀表的值:
{
"name": "foo.strong",
"description": "Foo value - strong reference (will persist)",
"measurements": [
{
"statistic": "VALUE",
"value": 10
}
],
"availableTags": []
}
現在,我們再次看到了它的價值。
5. 結論
在本文中,我們探討了為什麼 Micrometer 計量器在監視記憶體物件時可能會傳回NaN值,以及這種行為與 Java 垃圾回收和 Micrometer 使用弱引用之間的關係。
我們使用 Spring Boot Actuator 重現了這個問題,分析了根本原因,並演示瞭如何透過保持對被監控對象的強引用或在Gauge建構器中顯式啟用強引用來修復它。透過這些方法,我們可以確保指標在暴露給 Prometheus 或任何其他監控系統時保持準確性和可靠性。
與往常一樣,本文中提供的程式碼可在 GitHub 上找到。