Java擷取Prometheus監控名額資料
一. 準備工作
1. 有可以被Prometheus監控的服務
沒有的話可以參考以下連結本地搭建:SpringBoot應用接入Prometheus+Grafana
2. 選擇我們調用遠端服務的方式
可以選擇RestTemplate 作為遠端調用工具,RestTemplate 内部預設用的是 jdk 自帶的
HttpURLConnection 發送請求的,性能上面并不是太突出。可以将其替換為 httpclient 或者 okhttp。
二. 實戰
1. 引入依賴
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
2. 編寫http請求工具
package com.alibaba.bizworks.om.account.prometheus;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
/**
* RestTemplate 遠端調用工具類
* @author gf
* @date 2022/10/24
*/
@Slf4j
public class RestTemplateUtils {
/**
* 讀取時間,自定義預設8s,0表示沒有逾時時間
*/
public static final int READ_TIMEOUT = 1000*8;
/**
* 連接配接時間,自定義預設8s,0表示沒有逾時時間
*/
public static final int CONNEC_TIMEOUT = 1000*8;
/**
* 重試次數,自定義預設1
*/
public static final int RETRY_COUNT = 1;
/**
* http 請求 GET
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String getHttp(String url, JSONObject params) {
String result = getHttp(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* http 請求 GET
*
* @param url 位址
* @param params 參數
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String getHttp(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理
url = expandURL(url, params);
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.getForObject(url, String.class, params);
log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【GET/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* https 請求 GET
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String getHttps(String url, JSONObject params) {
String result = getHttps(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* https 請求 GET
*
* @param url 位址
* @param params 參數
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String getHttps(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = restTemplate();
RestTemplateUtils.clientHttpRequestFactory();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); //error處理
restTemplate.setRequestFactory(clientHttpRequestFactory()); // 繞過https
url = expandURL(url, params);
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.getForObject(url, String.class, params);
log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【GET/HTTPS請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* @Title: URL拼接
* @MethodName: expandURL
* @param url
* @param jsonObject
* @Return java.lang.String
* @Exception
* @Description:
*/
private static String expandURL(String url,JSONObject jsonObject) {
StringBuilder sb = new StringBuilder(url);
sb.append("?");
Set<String> keys = jsonObject.keySet();
for (String key : keys) {
sb.append(key).append("=").append(jsonObject.getString(key)).append("&");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
/**
* 擷取RestTemplate執行個體對象,可自由調用其方法
*
* @return RestTemplate執行個體對象
*/
public static HttpClient httpClient() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
//設定信任ssl通路
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
httpClientBuilder.setSSLContext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
// 注冊http和https請求
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
//使用Httpclient連接配接池的方式配置(推薦),同時支援netty,okHttp以及其他http架構
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// 最大連接配接數
poolingHttpClientConnectionManager.setMaxTotal(1000);
// 同路由并發數
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
//配置連接配接池
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
// 重試次數
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(1, true));
//設定預設請求頭
List<Header> headers = new ArrayList<>();
httpClientBuilder.setDefaultHeaders(headers);
return httpClientBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
// 連接配接逾時(毫秒),這裡設定10秒
clientHttpRequestFactory.setConnectTimeout(10 * 1000);
// 資料讀取逾時時間(毫秒),這裡設定60秒
clientHttpRequestFactory.setReadTimeout(60 * 1000);
// 從連接配接池擷取請求連接配接的逾時時間(毫秒),不宜過長,必須設定,比如連接配接不夠用時,時間過長将是災難性的
clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000);
return clientHttpRequestFactory;
}
public static RestTemplate restTemplate(){
//建立RestTemplate的時候,指定ClientHttpRequestFactory
return new RestTemplate(clientHttpRequestFactory());
}
}
3. 編寫響應體
從浏覽器中找到peomrtheus它擷取服務名額的請求位址
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnLyczY4cjNzQDO0AjZ0MGN4I2NyQzN3EWY4ETNkRGNmRzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
然後根據這個位址在postman發get請求拿到它的響應體結構
接下來我們就可以根據這個結果來封裝相應體了
@Data
public class PromResponceInfo {
/**
* 狀态
* 成功-- success
*/
private String status;
/**
* prometheus名額屬性和值
*/
private PromDataInfo data;
}
這裡隻關注後面的resultType 和result參數
@Data
public class PromDataInfo {
/**
* prometheus監控服務指表參數
*/
private List droppedTargets;
private List<PromResultInfo> activeTargets;
/**
* prometheus監控樣本名額參數
*/
private String resultType;
private List<PromMetric> result;
}
@Data
public class PromMetric {
/**
* metric name和描述目前樣本特征的labelsets
*/
private PromMetricInfo metric;
/**
* 一個float64的浮點型資料表示目前樣本的值。
*/
private String[] value;
}
@Data
public class PromMetricInfo {
/**
* prometheus名額名稱
*/
private String __name__;
/**
* prometheus執行個體名稱
*/
private String instance;
/**
* prometheus任務名稱
*/
private String job;
private String application;
private String exception;
private String method;
private String outcome;
private String status;
private String url;
}
到這裡我們響應體的結構就寫好了,還有一個名額參數,我這裡寫了一個類,後面需要查詢名額或者一些聚合函數(PromQL)
package com.alibaba.bizworks.om.account.prometheus;
import lombok.Data;
/**
* @Title: prometheus常量資訊
* @author gf
* @date 2022/10/19
*/
@Data
public class PromConstants {
/**
* prometheus-查詢SUCCESS
*/
public static final String SUCCESS = "success";
/**prometheus-查詢參數*/
public static final String QUERY = "query";
/**系統CPU使用率*/
public static final String SYSTEM_CPU_USAGE= "system_cpu_usage";
/**Java虛拟機可用的處理器數量*/
public static final String SYSTEM_CPU_COUNT= "system_cpu_count";
/**JVM的CPU使用率*/
public static final String PROCESS_CPU_COUNT= "process_cpu_usage";
/**
* tomcat相關參數
*/
/**tomcat_目前活躍會話數*/
public static final String TOMCAT_SESSIONS_ACTIVE_CURRENT_SESSIONS = "tomcat_sessions_active_current_sessions";
/**
* jvm 相關參數
*/
/**Java虛拟機的正常運作時間*/
public static final String PROCESS_UPTIME_SECONDS = "process_uptime_seconds";
/**可供Java虛拟機使用的已送出的記憶體量*/
public static final String JVM_MEMORT_COMMITTED_BYTES = "jvm_memory_committed_bytes";
/**自Java虛拟機啟動或重置峰值以來的活動線程峰值*/
public static final String JVM_THREADS_PEAK_THREADS= "jvm_threads_peak_threads";
/**在一個GC之後到下一個GC之前增加年輕代記憶體池的大小*/
public static final String JVM_GC_MEMORT_ALLOCATED_BYTES_TOTAL= "jvm_gc_memory_allocated_bytes_total";
/**程序的開始時間*/
public static final String PROCESS_START_TIME_SECONDS= "process_start_time_seconds";
/**最大記憶體*/
public static final String JVM_MEMORT_MAX_BYTES= "jvm_memory_max_bytes";
/**已使用記憶體*/
public static final String JVM_MEMORT_USED_BYTES= "jvm_memory_used_bytes";
/**請求次數*/
public static final String HTTP_SERVER_REQUEST_SECONDS_COUNT= "http_server_requests_seconds_count";
/**請求n次花費的時間*/
public static final String HTTP_SERVER_REQUEST_SECONDS_SUM= "http_server_requests_seconds_sum";
/**最長一次花了多長時間*/
public static final String HTTP_SERVER_REQUEST_SECONDS_MAX= "http_server_requests_seconds_max";
/**日志總數*/
public static final String LOGBACK_EVENTS_TOTAL = "logback_events_total";
}
4. 編寫接口測試
參數說明:
- promURL:請求url
- promQL:名額參數
@Slf4j
public class TestController {
public static PromDataInfo getDateInfo(String promURL, String promQL) {
log.info("請求位址:{},請求QL:{}", promURL, promQL);
JSONObject param = new JSONObject();
param.put(PromConstants.QUERY, promQL);
String http = null;
try {
http = RestTemplateUtils.getHttp(promURL, param);
} catch (Exception e) {
log.error("請求位址:{},請求QL:{},異常資訊:{}", promURL, promQL, e);
}
PromResponceInfo responceInfo = JSON.parseObject(http, PromResponceInfo.class);
log.info("請求位址:{},請求QL:{},傳回資訊:{}", promURL, promQL, responceInfo);
if (Objects.isNull(responceInfo)) {
return null;
}
String status = responceInfo.getStatus();
if (StringUtils.isBlank(status)
|| !PromConstants.SUCCESS.equals(status)
) {
return null;
}
PromDataInfo data = responceInfo.getData();
return data;
}
public static void main(String[] args) {
PromDataInfo dateInfo= getDateInfo("http://localhost:9090/api/v1/query", PromConstants.HTTP_SERVER_REQUEST_SECONDS_COUNT);
System.out.println(dateInfo);
}
}
5. 測試結果
我們可以debug檢視擷取到的資訊![]()
Java擷取Prometheus監控名額資料