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讀取樣本資料
&n