Prometheus自定義指標


1.  自定義指標

為了注冊自定義指標,請將MeterRegistry注入到組件中,例如:

public class Dictionary {

    private final List<String> words = new CopyOnWriteArrayList<>();

    Dictionary(MeterRegistry registry) {
        registry.gaugeCollectionSize("dictionary.size", Tags.empty(), this.words);
    }

    // ...

}

如果你的指標依賴於其它bean,那么推薦使用MeterBinder注冊這些指標,例如:

@Bean
MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

使用MeterBinder可以確保設置正確的依賴關系,並且在檢索指標的值時bean是可用的。默認情況下,來自所有MeterBinder bean的指標將自動綁定到Spring管理的MeterRegistry。如果您發現在組件或應用程序之間重復檢測一個指標,那么MeterBinder實現也會很有用。 

文檔參見

https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-export-prometheus

https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-custom

 

接下來,還是用之前的prometheus-example那個例子,我們來自定義業務指標

重新回顧一下

依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <scope>runtime</scope>
</dependency>

application.yml

spring:
  application:
    name: prometheus-example
management:
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    tags:
      application: ${spring.application.name}

prometheus.yml

scrape_configs:
  - job_name: 'springboot-prometheus'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['192.168.100.93:8080','192.168.100.16:8080']

啟動項目

# 啟動Prometheus
./prometheus --config.file=prometheus.yml

# 啟動Grafana
bin/grafana-server web

下面改造一下,新增一個AOP來模擬記錄訂單相關指標

package com.cjs.example.aop;

import com.cjs.example.domain.OrderVO;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author ChengJianSheng
 * @since 2021/3/8
 */
@Aspect
@Component
public class OrderAspect {

    private Counter orderCounter;

    private DistributionSummary orderSummary;

    public OrderAspect(MeterRegistry registry) {
        orderCounter = registry.counter("order_quantity_total", "status", "success");
        orderSummary = registry.summary("order_amount_total", "status", "success");
    }

// @PostConstruct
// public void init() {
//
// }


    @Pointcut("execution(public * com.cjs.example.controller.OrderController.createOrder(..))")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed();
        OrderVO orderVO = (OrderVO) result;

        orderCounter.increment();
        orderSummary.record(orderVO.getAmount().doubleValue());

        return result;
    }

} 

項目結構如圖

用postman造幾條數據

為了好看,我們在Grafana上創建一個dashboard,其中包含4個面板,對應四個指標

輸入指標、設置名稱、選擇視圖、設置屬性

最后,記得保存。現在,我們有三個儀表盤了

2.  自動發現抓取目標

在實際項目中,我們不可能一個一個手動的配置要抓取的目標,每次都去修改prometheus.yml文件,然后再重啟服務,想都不要想,不可能這么做。

為此,我們需要動態發現目標。Prometheus支持很多的服務發現配置,比如:zookeeper、eureka、kubernetes等等

詳見 https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config

這里以Eureka為例,看看Prometheus如何從eureka中動態發現服務

https://prometheus.io/docs/prometheus/latest/configuration/configuration/#eureka_sd_config

https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config

首先,我們創建一個項目當Eureka Server,並啟動它

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server</name>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
fetchRegistry: false
serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 

啟動類上加@EnableEurekaServer 

eureka server 啟動以后,接下來,我們改造一下剛才的項目prometheus-example

首先引入eureka client,這樣的話完成的pom.xml就變成這樣了

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cjs.example</groupId>
    <artifactId>prometheus-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>prometheus-example</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

修改application.yml

這里有個地方要注意,原來我們沒有加上下文路徑(server.servlet.context-path),但是一般項目是會設置的,所以這次我們也加上。

(PS:項目GitHub地址  https://github.com/chengjiansheng/prometheus-example

完整的配置如下:

server:
  port: 8080
  servlet:
    context-path: /hello

spring:
  application:
    name: prometheus-example

management:
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    tags:
      application: ${spring.application.name}

eureka:
  client:
    serviceUrl:
      defaultZone: http://192.168.100.93:8761/eureka/
  instance:
    metadata-map:
      "prometheus.scrape": "true"
      "prometheus.path": "${server.servlet.context-path}/actuator/prometheus"
      "prometheus.port": "${server.port}" 

注意:

1、加了server.servlet.context-path以后,抓取的路徑就不再是 http://192.168.100.93:8080/actuator/prometheus了,而是變成了 http://192.168.100.93:8080/hello/actuator/prometheus了。之前我們prometheus.yml文件里靜態配置抓取目標的metrics_path是/actuator/prometheus,但是現在不能這樣寫了,因為加了應用上下文路徑,而且每個服務都不一樣。

2、為了能夠根據各服務動態自定義指標路徑(metrics_path),最最重要的是下面這三行

eureka:
  instance:
    metadata-map:
      "prometheus.scrape": "true"
      "prometheus.path": "${server.servlet.context-path}/actuator/prometheus"
      "prometheus.port": "${server.port}" 

prometheus是通過eureka發現服務的,因此只有將服務的指標路徑(抓取地址)寫到eureka里,prometheus才能拿到

換言之,只有服務在注冊的時候,將自己暴露的端點(endpoint)以元數據的方式寫到eureka中prometheus才能正確的從目標抓取數據

修改prometheus.yml,改為通過eureka獲取抓取目標

scrape_configs:
  - job_name: 'eureka-prometheus'
    eureka_sd_configs:
      - server: http://192.168.100.93:8761/eureka
    relabel_configs:
      - source_labels: [__meta_eureka_app_instance_metadata_prometheus_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)

 

https://github.com/prometheus/prometheus/blob/release-2.25/documentation/examples/prometheus-eureka.yml

https://github.com/prometheus/prometheus/blob/main/documentation/examples/prometheus-eureka.yml

https://github.com/prometheus/prometheus/tree/main/documentation/examples

這里不得不提的是relabel_configs

Relabeling(重新標記)是一種強大的工具,可以在抓取目標之前動態重寫目標的標簽集。每個抓取配置可以配置多個重新標記步驟。 它們按照在配置文件中出現的順序應用於每個目標的標簽集。

Relabeling是在抓取(scraping)前修改target和它的labels

3.  補充:Prometheus存儲

Prometheus自帶一個本地磁盤時間序列數據庫,但也可以選擇與遠程存儲系統集成。

本地存儲

Prometheus的本地時間序列數據庫在本地存儲上以定制的、高效的格式存儲數據。

注意,本地存儲的一個限制是它沒有集群或副本。因此,在驅動器或節點中斷時,它不是任意可伸縮或持久的,應該像任何其他單節點數據庫一樣進行管理。建議使用RAID來提高存儲可用性,建議使用快照作為備份。使用適當的架構,可以在本地存儲中保留多年的數據。也可以采用外部存儲。 

TSDB (時間序列數據庫,簡稱時序數據庫)

Prometheus具有幾個用於配置本地存儲的參數。 最重要的是:

  • --storage.tsdb.path: Prometheus寫入數據庫的位置,默認是data/
  • --storage.tsdb.retention.time: 什么時候刪除舊數據,默認是15天
  • --storage.tsdb.retention.size: 要保留的最大存儲塊字節數。最舊的數據將首先被刪除。默認為0或禁用。這個標志是實驗性的,在未來的版本中可能會改變。支持的單位:B、KB、MB、GB、TB、PB、EB。例如:“512 mb” 

Prometheus平均每個樣本僅存儲1~2個字節.因此,要規划Prometheus服務器的容量,可以使用以下公式粗略計算:

 needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample

Prometheus通過以下三種方式與遠程存儲系統集成:

  • Prometheus可以將其提取的樣本以標准格式寫入遠程URL
  • Prometheus可以以標准格式從其他Prometheus服務器接收樣本
  • Prometheus可以以標准格式從遠程URL讀取樣本數據 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM