1. 程式人生 > >自定義Metrics:讓Prometheus監控你的應用程式

自定義Metrics:讓Prometheus監控你的應用程式

前言

Prometheus社群提供了大量的官方以及第三方Exporters,可以滿足Prometheus的採納者快速實現對關鍵業務,以及基礎設施的監控需求。



如上所示,一個簡單的應用以及環境架構。一般而言,我們通常會從幾個層面進行監控指標的採集:

  • 入口閘道器:這裡可以是Nginx/HaProxy這一類的負載均衡器,也可以是注入Spring Cloud Zuul這一類框架提供的微服務入口。一般來說我們需要對所有Http Request相關的指標資料進行採集。如請求地址,Http Method,返回狀態碼,響應時長等。從而可以通過這些指標歷史資料去分析業務壓力,服務狀態等資訊。
  • 應用服務:對於應用服務而言,基本的如應用本身的資源使用率,比如如果是Java類程式可以直接通過JVM資訊來進行統計,如果是部署到容器中,則可以通過Container的資源使用情況來統計。除了資源用量外,某些特殊情況下,我們可能還會對應用中的某些業務指標進行採集。
  • 基礎設施:虛擬機器或者物理機的資源使用情況等。
  • 其它:叢集環境中所使用到的資料庫,快取,訊息佇列等中介軟體狀態等。


對於以上的集中場景中,除了直接使用Prometheus社群提供的Exporter外,不同的專案可能還需要實現一些自定義的Exporter用於實現對於特定目的的指標的採集和監控需求。

本文將以Spring Boot/Spring Cloud為例,介紹如果使用Prometheus SDK實現自定義監控指標的定義以及暴露,並且會介紹Prometheus中四種不同指標型別(Counter, Gauge, Histogram, Summary)的實際使用場景;

擴充套件Spring應用程式,支援Prometheus採集

新增Prometheus Java Client依賴

> 這裡使用0.0.24的版本,在之前的版本中Spring Boot暴露的監控地址,無法正確的處理Prometheus Server的請求,詳情:https://github.com/prometheus/ ... s/265

build.gradle

dependencies {
...
compile 'io.prometheus:simpleclient:0.0.24'
compile "io.prometheus:simpleclient_spring_boot:0.0.24"
compile "io.prometheus:simpleclient_hotspot:0.0.24"
}

啟用Prometheus Metrics Endpoint

添加註解@EnablePrometheusEndpoint啟用Prometheus Endpoint,這裡同時使用了simpleclient_hotspot中提供的DefaultExporter該Exporter會在metrics endpoint中放回當前應用JVM的相關資訊

@SpringBootApplication
@EnablePrometheusEndpoint
public class SpringApplication implements CommandLineRunner {

public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
}

@Override
public void run(String... strings) throws Exception {
    DefaultExports.initialize();
}
}



預設情況下Prometheus暴露的metrics endpoint為 /prometheus,可以通過endpoint配置進行修改

endpoints:
prometheus:
id: metrics
metrics:
id: springmetrics
sensitive: false
enabled: true



啟動應用程式訪問 http://localhost:8080/metrics 可以看到以下輸出:

HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds.

TYPE jvm_gc_collection_seconds summary

jvm_gc_collection_seconds_count{gc="PS Scavenge",} 11.0
jvm_gc_collection_seconds_sum{gc="PS Scavenge",} 0.18
jvm_gc_collection_seconds_count{gc="PS MarkSweep",} 2.0
jvm_gc_collection_seconds_sum{gc="PS MarkSweep",} 0.121

HELP jvm_classes_loaded The number of classes that are currently loaded in the JVM

TYPE jvm_classes_loaded gauge

jvm_classes_loaded 8376.0

HELP jvm_classes_loaded_total The total number of classes that have been loaded since the JVM has started execution

TYPE jvm_classes_loaded_total counter

...

 

新增攔截器,為監控埋點做準備

除了獲取應用JVM相關的狀態以外,我們還可能需要新增一些自定義的監控Metrics實現對系統性能,以及業務狀態進行採集,以提供日後優化的相關支撐資料。首先我們使用攔截器處理對應用的所有請求。

繼承WebMvcConfigurerAdapter類,複寫addInterceptors方法,對所有請求/**新增攔截器

@SpringBootApplication
@EnablePrometheusEndpoint
public class SpringApplication extends WebMvcConfigurerAdapter implements CommandLineRunner {
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new PrometheusMetricsInterceptor()).addPathPatterns("/**");
}
}


PrometheusMetricsInterceptor整合HandlerInterceptorAdapter,通過複寫父方法,實現對請求處理前/處理完成的處理。

public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return super.preHandle(request, response, handler);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    super.afterCompletion(request, response, handler, ex);
}
}

自定義Metrics指標

Prometheus提供了4中不同的Metrics型別:Counter,Gauge,Histogram,Summary

1)Counter:只增不減的計數器

計數器可以用於記錄只會增加不會減少的指標型別,比如記錄應用請求的總量(http_requests_total),cpu使用時間(process_cpu_seconds_total)等。

對於Counter型別的指標,只包含一個inc()方法,用於計數器+1

一般而言,Counter型別的metrics指標在命名中我們使用_total結束。

public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {

static final Counter requestCounter = Counter.build()
        .name("io_namespace_http_requests_total").labelNames("path", "method", "code")
        .help("Total requests.").register();

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    String requestURI = request.getRequestURI();
    String method = request.getMethod();
    int status = response.getStatus();

    requestCounter.labels(requestURI, method, String.valueOf(status)).inc();
    super.afterCompletion(request, response, handler, ex);
}
}


使用Counter.build()建立Counter metrics,name()方法,用於指定該指標的名稱 labelNames()方法,用於宣告該metrics擁有的維度label。在addInterceptors方法中,我們獲取當前請求的,RequesPath,Method以及狀態碼。並且呼叫inc()方法,在每次請求發生時計數+1。

Counter.build()...register(),會像Collector中註冊該指標,並且當訪問/metrics地址時,返回該指標的狀態。

通過指標io_namespace_http_requests_total我們可以:

  • 查詢應用的請求總量

PromQL

sum(io_namespace_http_requests_total)


  • 查詢每秒Http請求量

PromQL

sum(rate(io_wise2c_gateway_requests_total[5m]))



  • 查詢當前應用請求量Top N的URI

PromQL

topk(10, sum(io_namespace_http_requests_total) by (path))

2)Gauge: 可增可減的儀表盤

對於這類可增可減的指標,可以用於反應應用的__當前狀態__,例如在監控主機時,主機當前空閒的記憶體大小(node_memory_MemFree),可用記憶體大小(node_memory_MemAvailable)。或者容器當前的cpu使用率,記憶體使用率。

對於Gauge指標的物件則包含兩個主要的方法inc()以及dec(),使用者新增或者減少計數。在這裡我們使用Gauge記錄當前正在處理的Http請求數量。

public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {

...省略的程式碼
static final Gauge inprogressRequests = Gauge.build()
        .name("io_namespace_http_inprogress_requests").labelNames("path", "method", "code")
        .help("Inprogress requests.").register();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ...省略的程式碼
    // 計數器+1
    inprogressRequests.labels(requestURI, method, String.valueOf(status)).inc();
    return super.preHandle(request, response, handler);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ...省略的程式碼
    // 計數器-1
    inprogressRequests.labels(requestURI, method, String.valueOf(status)).dec();

    super.afterCompletion(request, response, handler, ex);
}
}



通過指標io_namespace_http_inprogress_requests我們可以直接查詢應用當前正在處理中的Http請求數量:

PromQL

io_namespace_http_inprogress_requests{}



3)Histogram:自帶buckets區間用於統計分佈統計圖

主要用於在指定分佈範圍內(Buckets)記錄大小(如http request bytes)或者事件發生的次數。

以請求響應時間requests_latency_seconds為例,假如我們需要記錄http請求響應時間符合在分佈範圍{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}中的次數時。

public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {

static final Histogram requestLatencyHistogram = Histogram.build().labelNames("path", "method", "code")
        .name("io_namespace_http_requests_latency_seconds_histogram").help("Request latency in seconds.")
        .register();

private Histogram.Timer histogramRequestTimer;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ...省略的程式碼
    histogramRequestTimer = requestLatencyHistogram.labels(requestURI, method, String.valueOf(status)).startTimer();
    ...省略的程式碼
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ...省略的程式碼
    histogramRequestTimer.observeDuration();
    ...省略的程式碼
}
}


使用Histogram構造器可以建立Histogram監控指標。預設的buckets範圍為{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}。如何需要覆蓋預設的buckets,可以使用.buckets(double... buckets)覆蓋。

Histogram會自動建立3個指標,分別為:

  • 事件發生總次數: basename_count

 

實際含義: 當前一共發生了2次http請求

io_namespace_http_requests_latency_seconds_histogram_count{path="/",method="GET",code="200",} 2.0

 

  • 所有事件產生值的大小的總和: basename_sum

 

實際含義: 發生的2次http請求總的響應時間為13.107670803000001 秒

io_namespace_http_requests_latency_seconds_histogram_sum{path="/",method="GET",code="200",} 13.107670803000001

 

  • 事件產生的值分佈在bucket中的次數: basename_bucket{le="上包含"}

在總共2次請求當中,http請求響應時間 <=0.005 秒 的請求次數為0

io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.005",} 0.0

在總共2次請求當中,http請求響應時間 <=0.01 秒 的請求次數為0

io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.01",} 0.0

在總共2次請求當中,http請求響應時間 <=0.025 秒 的請求次數為0

io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.025",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.05",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.075",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.1",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.25",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.5",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.75",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="1.0",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="2.5",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="5.0",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="7.5",} 2.0

在總共2次請求當中,http請求響應時間 <=10 秒 的請求次數為0

io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="10.0",} 2.0

在總共2次請求當中,ttp請求響應時間 10 秒 的請求次數為0

io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="+Inf",} 2.0

Summary: 客戶端定義的資料分佈統計圖

Summary和Histogram非常型別相似,都可以統計事件發生的次數或者大小,以及其分佈情況。

Summary和Histogram都提供了對於事件的計數_count以及值的彙總_sum。 因此使用_count,和_sum時間序列可以計算出相同的內容,例如http每秒的平均響應時間:rate(basename_sum[5m]) / rate(basename_count[5m])。

同時Summary和Histogram都可以計算和統計樣本的分佈情況,比如中位數,9分位數等等。其中 0.0<= 分位數Quantiles <= 1.0。

不同在於Histogram可以通過histogram_quantile函式在伺服器端計算分位數。 而Sumamry的分位數則是直接在客戶端進行定義。因此對於分位數的計算。 Summary在通過PromQL進行查詢時有更好的效能表現,而Histogram則會消耗更多的資源。相對的對於客戶端而言Histogram消耗的資源更少。

public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {

static final Summary requestLatency = Summary.build()
        .name("io_namespace_http_requests_latency_seconds_summary")
        .quantile(0.5, 0.05)
        .quantile(0.9, 0.01)
        .labelNames("path", "method", "code")
        .help("Request latency in seconds.").register();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ...省略的程式碼
    requestTimer = requestLatency.labels(requestURI, method, String.valueOf(status)).startTimer();
    ...省略的程式碼
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ...省略的程式碼
    requestTimer.observeDuration();
    ...省略的程式碼
}
}


使用Summary指標,會自動建立多個時間序列:

  • 事件發生總的次數

含義:當前http請求發生總次數為12次

io_namespace_http_requests_latency_seconds_summary_count{path="/",method="GET",code="200",} 12.0
  • 事件產生的值的總和

含義:這12次http請求的總響應時間為 51.029495508s

io_namespace_http_requests_latency_seconds_summary_sum{path="/",method="GET",code="200",} 51.029495508
  • 事件產生的值的分佈情況

含義:這12次http請求響應時間的中位數是3.052404983s

io_namespace_http_requests_latency_seconds_summary{path="/",method="GET",code="200",quantile="0.5",} 3.052404983

含義:這12次http請求響應時間的9分位數是8.003261666s

io_namespace_http_requests_latency_seconds_summary{path="/",method="GET",code="200",quantile="0.9",} 8.003261666

 

使用Collector暴露業務指標

除了在攔截器中使用Prometheus提供的Counter,Summary,Gauage等構造監控指標以外,我們還可以通過自定義的Collector實現對相關業務指標的暴露

@SpringBootApplication
@EnablePrometheusEndpoint
public class SpringApplication extends WebMvcConfigurerAdapter implements CommandLineRunner {

@Autowired
private CustomExporter customExporter;

...省略的程式碼

@Override
public void run(String... args) throws Exception {
    ...省略的程式碼
    customExporter.register();
}
}



CustomExporter整合自io.prometheus.client.Collector,在呼叫Collector的register()方法後,當訪問/metrics時,則會自動從Collector的collection()方法中獲取採集到的監控指標。

由於這裡CustomExporter存在於Spring的IOC容器當中,這裡可以直接訪問業務程式碼,返回需要的業務相關的指標。

import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Component
public class CustomExporter extends Collector {
@Override
public List<MetricFamilySamples> collect() {
    List<MetricFamilySamples> mfs = new ArrayList<>();

    # 建立metrics指標
    GaugeMetricFamily labeledGauge =
            new GaugeMetricFamily("io_namespace_custom_metrics", "custom metrics", Collections.singletonList("labelname"));

    # 設定指標的label以及value
    labeledGauge.addMetric(Collections.singletonList("labelvalue"), 1);

    mfs.add(labeledGauge);
    return mfs;
}
}



當然這裡也可以使用CounterMetricFamily,SummaryMetricFamily宣告其它的指標型別。

小結

好了。 目前為止,啟動應用程式,並且訪問 http://localhost:8080/metrics。我們可以看到如下結果。


這部分分別介紹了兩種方式,在Spring應用中實現對於自定義Metrics指標的定義:

  • 攔截器/過濾器:用於統計所有應用請求的情況
  • 自定義Collector: 可以用於統計應用業務能力相關的監控情況


同時介紹了4中Metrics指標型別以及使用場景:

    • Counter,只增不減的計數器
    • Gauge,可增可減的儀表盤
    • Histogram,自帶buckets區間用於統計分佈統計圖
    • Summary, 客戶端定義的資料分佈統計圖