天天看點

Java擷取Prometheus監控名額資料

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它擷取服務名額的請求位址
Java擷取Prometheus監控名額資料
然後根據這個位址在postman發get請求拿到它的響應體結構
Java擷取Prometheus監控名額資料
接下來我們就可以根據這個結果來封裝相應體了
@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監控名額資料