Springboot远程调用Prometheus Api获取指标数据
0、写在前面
1> 使用RestTemplate作为远程调用工具调用prometheus原生api获取数据
2> prometheus原生api文档地址如下:https://prometheus.io/docs/prometheus/latest/querying/api/
3> 通过访问prometheus原生api,查看原生api返回的数据格式,定义对应的实体类格式
4> 下面所列功能代码,仅为部分调用api结果,仅供参考,如若需要调用其他api,可自行编写对应方法
1、远程调用类
1.1、pom依赖
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
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.client.support.BasicAuthenticationInterceptor;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
* RestTemplate工具类
*
* @author 星空流年
* @date 2023/7/10
*/
@Slf4j
@Component
@SuppressWarnings("all")
public class RestTemplateUtils {
/**
* http 请求 GET
*
* @param url 地址
* @param params 参数
* @return Http连接
*/
public String getHttp(String url, JSONObject params) {
return getRestConnection(url, params, "http");
}
/**
* https 请求 GET
*
* @param url 地址
* @param params 参数
* @return Https连接
*/
public String getHttps(String url, JSONObject params) {
return getRestConnection(url, params, "https");
}
/**
* 获取远程连接
*
* @param url 请求地址
* @param params JSON对象
* @param connectionFlag 请求标志
* @return 远程连接
*/
private String getRestConnection(String url, JSONObject params, String connectionFlag) {
String restConnection = null;
if (StringUtils.equals("http", connectionFlag)) {
restConnection = getRestHttpConnection(url, params, 10000, 60000, 3);
}
if (StringUtils.equals("https", connectionFlag)) {
restConnection = getRestHttpsConnection(url, params, 10000, 60000, 3);
}
return restConnection;
}
/**
* http 请求 GET
*
* @param url 地址
* @param params 参数
* @param connectTimeout 连接时间
* @param readTimeout 读取时间
* @param retryCount 重试机制
* @return 请求字符串
*/
public String getRestHttpConnection(String url, JSONObject params, int connectTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connectTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(requestFactory);
// 设置编码集
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 异常处理
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
// 获取URI
URI uri = getUriByUrl(url, params);
// 重试机制
for (int i = 1; i <= retryCount; i++) {
try {
// 此处设置值为认证的用户名和密码信息, 请注意修改
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("username", "password");
return restTemplate.getForEntity(uri, String.class).getBody();
} catch (Exception e) {
log.error("[GET/HTTP请求信息]异常, 重试次数:{}, 请求地址:{}, 请求参数:{}, 异常信息:{}", i, url, params, Throwables.getStackTraceAsString(e));
}
}
return null;
}
/**
* https 请求 GET
*
* @param url 地址
* @param params 参数
* @param connectTimeout 连接时间
* @param readTimeout 读取时间
* @param retryCount 重试机制
* @return 请求字符串
*/
public String getRestHttpsConnection(String url, JSONObject params, int connectTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connectTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = restTemplate();
clientHttpRequestFactory();
// 设置编码集
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 异常处理
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
// 绕过https
restTemplate.setRequestFactory(clientHttpRequestFactory());
// 获取URI
URI uri = getUriByUrl(url, params);
for (int i = 1; i <= retryCount; i++) {
try {
// 此处设置值为认证的用户名和密码信息, 请注意修改
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("username", "password");
return restTemplate.getForEntity(uri, String.class).getBody();
} catch (Exception e) {
log.error("[GET/HTTPS请求信息]异常, 重试次数:{}, 请求地址:{}, 请求参数:{}, 异常信息:{}", i, url, params, Throwables.getStackTraceAsString(e));
}
}
return null;
}
/**
* 获取RestTemplate实例对象,可自由调用其方法
*
* @return RestTemplate实例对象
*/
public 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(RestConnectionConstants.HTTP_CONNECTION_FLAG, PlainConnectionSocketFactory.getSocketFactory())
.register(RestConnectionConstants.HTTPS_CONNECTION_FLAG, sslConnectionSocketFactory).build();
//使用Httpclient连接池的方式配置
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);
// 设置请求连接超时时间
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000)
.setSocketTimeout(60000).build();
httpClientBuilder.setDefaultRequestConfig(requestConfig);
return (HttpClient) httpClientBuilder.build();
} catch (Exception e) {
throw new RestException(RestStatus.SYSTEM_ERROR, Throwables.getStackTraceAsString(e));
}
}
/**
* 创建RestTemplate
*
* @return RestTemplate
*/
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
/**
* 创建ClientHttpRequestFactory
*
* @return ClientHttpRequestFactory
*/
private ClientHttpRequestFactory clientHttpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
/**
* 通过URL获取URI
*
* @param url url
* @param params 请求参数
* @return {@code URI}
*/
private URI getUriByUrl(String url, JSONObject params) {
String query = "query";
if (!params.isEmpty()) {
// 网关针对URL中特殊字符进行加密访问, 这里针对网关未处理特殊字符参数进行转义处理
if (params.containsKey(query)) {
String replaceQuery = params.getString(query)
.replace("=", "%3D").replace(" ", "%20")
.replace("{", "%7B").replace("}", "%7D")
.replace("\"", "%22").replace("/", "%2F")
.replace("|", "%7C").replace("+", "%2B")
.replace("[", "%5B").replace("]", "%5D")
.replace("<", "%3C").replace(">", "%3E")
.replace("\n", "%20");
params.put(query, replaceQuery);
} else {
params.keySet().forEach(key -> {
String decode = URLDecoder.decode(params.getString(key), StandardCharsets.UTF_8);
params.put(key, decode);
});
}
url = expandUrl(url, params);
}
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
if (params.containsKey(query)) {
return builder.build(true).toUri();
} else {
return builder.build().encode().toUri();
}
}
/**
* URL拼接
*
* @param url 请求URL
* @param jsonObject JSON对象
* @return 拼接之后的URL
*/
private String expandUrl(String url, JSONObject jsonObject) {
HashMap<String, Object> paramMap = new HashMap<>(16);
StringBuilder stringBuilder = new StringBuilder(url);
stringBuilder.append("?");
Set<String> keys = jsonObject.keySet();
keys.forEach(key -> paramMap.put(key, jsonObject.getString(key)));
String joinStr = Joiner.on("&").withKeyValueSeparator("=").join(paramMap);
return stringBuilder.append(joinStr).toString();
}
}
2、即时查询获取数据
Prometheus查询结果实体类:PromQueryResult
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* Prometheus查询结果对象信息
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryResult {
/**
* prometheus指标属性
*/
private Map<String, Object> metric;
/**
* prometheus即时查询指标值
*/
private List<String> value;
}
Prometheus查询数据结果实体类:PromQueryData
import lombok.Data;
import java.util.List;
/**
* Prometheus查询数据结果对象
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryData {
/**
* prometheus结果类型
* vector--瞬时向量
* matrix--区间向量
* scalar--标量
* string--字符串
*/
private String resultType;
/**
* prometheus指标属性和值
*/
private List<PromQueryResult> result;
}
Prometheus查询响应实体类:PromQueryResponse
import lombok.Data;
/**
* Prometheus查询响应对象
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryResponse {
/**
* 状态
* 成功-- success
*/
private String status;
/**
* prometheus指标属性和值
*/
private PromQueryData data;
}
2.2、即时查询接口实现
即时查询接口类:PromQueryService
import xxx.entity.pojo.PromQueryData;
/**
* 指标查询接口类
*
* @author 星空流年
* @date 2023/7/10
*/
public interface PromQueryService {
/**
* Prometheus即时查询
*
* @param query 查询
* @param time 时间戳, 单位: 秒
* @return {@code PromQueryData}
*/
PromQueryData getQueryDataInfo(String query, String time);
}
即时查询接口实现类:PromQueryServiceImpl
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import xxx.entity.pojo.PromQueryData;
import xxx.entity.pojo.PromQueryResponse;
import xxx.service.PromQueryService;
import xxx.util.RestTemplateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 指标查询接口实现类
*
* @author 星空流年
* @date 2023/7/10
*/
@Slf4j
@Service("promQueryService")
public class PromQueryServiceImpl implements PromQueryService {
@Resource
private RestTemplateUtils restTemplateUtils;
@Override
public PromQueryData getQueryDataInfo(String query, String time) {
if (StringUtils.isBlank(time)) {
time = String.valueOf(DateUtil.currentSeconds());
}
JSONObject param = new JSONObject();
param.put("query", query);
param.put("time", time);
// prometheus的URL连接地址, 根据需要修改
String url = "http://localhost:9090" + "/api/v1/query";
return (PromQueryData) getDataInfo(url, param);
}
/**
* 获取查询结果数据
*
* @param promUrl 调用的prometheus的URL
* @param param 请求参数
* @return 查询结果对象
*/
private Object getDataInfo(String promUrl, JSONObject param) {
String http = getHttp(promUrl, param);
PromQueryResponse responseInfo = JSON.parseObject(http, PromQueryResponse.class);
log.info("即时查询请求地址: {}, 请求参数: {}", promUrl, param);
if (Objects.isNull(responseInfo)) {
return null;
}
String status = responseInfo.getStatus();
if (StringUtils.isBlank(status) || !StringUtils.equals("success", status)) {
return null;
}
return responseInfo.getData();
}
/**
* 获取http连接
*
* @param promUrl 连接URL
* @param param 请求参数
* @return http连接
*/
private String getHttp(String promUrl, JSONObject param) {
String http = null;
try {
http = restTemplateUtils.getHttp(promUrl, param);
} catch (Exception e) {
log.error("请求地址: {}, 请求参数: {}, 异常信息: {}", promUrl, param, Throwables.getStackTraceAsString(e));
}
return http;
}
}
3、范围查询获取数据
3.1、范围查询实体类
范围查询实体类:PromQueryRange
import lombok.Data;
/**
* Prometheus范围查询实体类
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryRange {
/**
* 查询指标
*/
private String query;
/**
* 区间范围查询开始时间
* 格式为:时分秒时间戳
*/
private String start;
/**
* 区间范围查询结束时间
* 格式为:时分秒时间戳
*/
private String end;
/**
* 时间区间步长, 即:时间间隔
*/
private Integer step;
}
Prometheus范围区间查询结果实体类:PromQueryRangeResult
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* Prometheus范围区间查询结果对象信息
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryRangeResult {
/**
* prometheus指标属性
*/
private Map<String, Object> metric;
/**
* prometheus范围查询指标值
*/
private List<List<String>> values;
}
Prometheus范围区间查询数据结果对象实体类:PromQueryRangeData
import lombok.Data;
import java.util.List;
/**
* Prometheus范围区间查询数据结果对象
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryRangeData {
/**
* prometheus结果类型
* vector--瞬时向量
* matrix--区间向量
* scalar--标量
* string--字符串
*/
private String resultType;
/**
* prometheus指标属性和值
*/
private List<PromQueryRangeResult> result;
}
Prometheus范围区间查询响应对象实体类:PromQueryRangeResponse
import lombok.Data;
/**
* Prometheus范围区间查询响应对象
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromQueryRangeResponse {
/**
* 状态
* 成功-- success
*/
private String status;
/**
* prometheus范围查询指标属性和值
*/
private PromQueryRangeData data;
}
3.2、范围查询接口实现
范围查询接口类:PromQueryService
import xxx.entity.pojo.PromQueryRangeData;
import xxx.entity.pojo.PromQueryRange;
/**
* 指标查询接口类
*
* @author 星空流年
* @date 2023/7/10
*/
public interface PromQueryService {
/**
* Prometheus范围区间查询
*
* @param queryRangeDto 查询范围类
* @return {@code PromQueryRangeData}
*/
PromQueryRangeData getQueryRangeDataInfo(PromQueryRange queryRange);
}
范围查询接口实现类:PromQueryServiceImpl
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import xxx.entity.pojo.PromQueryRange;
import xxx.entity.pojo.PromQueryRangeData;
import xxx.entity.pojo.PromQueryRangeResponse;
import xxx.service.PromQueryService;
import xxx.util.RestTemplateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 指标查询接口实现类
*
* @author 星空流年
* @date 2023/7/10
*/
@Slf4j
@Service("promQueryService")
public class PromQueryServiceImpl implements PromQueryService {
@Resource
private RestTemplateUtils restTemplateUtils;
@Override
public PromQueryRangeData getQueryRangeDataInfo(PromQueryRange queryRange) {
JSONObject param = new JSONObject();
handleQueryRangeParams(param, queryRange);
// prometheus的URL连接地址, 根据需要修改
String url = "http://localhost:9090" + "/api/v1/query_range";
return (PromQueryRangeData) getDataInfo(url, param);
}
/**
* 处理范围查询参数
*
* @param param 参数
* @param queryRange PromQueryRange对象
*/
private void handleQueryRangeParams(JSONObject param, PromQueryRange queryRange) {
String start = queryRange.getStart();
if (StringUtils.isBlank(start)) {
// 开始时间为空, 则设置默认值为当前时间
start = String.valueOf(DateUtil.currentSeconds());
}
String end = queryRange.getEnd();
if (StringUtils.isBlank(end)) {
// 结束时间为空, 则设置默认值为当前时间向后偏移1小时
end = String.valueOf(DateUtil.offsetHour(DateUtil.parse(start), 1).getTime());
}
param.put("query", queryRange.getQuery());
param.put("start", start);
param.put("end", end);
param.put("step", queryRange.getStep());
}
/**
* 获取查询结果数据
*
* @param promUrl 调用的prometheus的URL
* @param param 请求参数
* @return 查询结果对象
*/
private Object getDataInfo(String promUrl, JSONObject param) {
String http = getHttp(promUrl, param);
PromQueryRangeResponse rangeResponse = JSON.parseObject(http, PromQueryRangeResponse.class);
log.info("范围区间查询请求地址: {}, 请求参数: {}", promUrl, param);
if (Objects.isNull(rangeResponse)) {
return null;
}
String status = rangeResponse.getStatus();
if (StringUtils.isBlank(status) || !StringUtils.equals("success", status)) {
return null;
}
return rangeResponse.getData();
}
/**
* 获取http连接
*
* @param promUrl 连接URL
* @param param 请求参数
* @return http连接
*/
private String getHttp(String promUrl, JSONObject param) {
String http = null;
try {
http = restTemplateUtils.getHttp(promUrl, param);
} catch (Exception e) {
log.error("请求地址: {}, 请求参数: {}, 异常信息: {}", promUrl, param, Throwables.getStackTraceAsString(e));
}
return http;
}
}
4、根据标签匹配器获取时序数据
4.1、时序数据实体类
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.List;
/**
* Prometheus时序对象
*
* @author 星空流年
* @date 2023/7/10
*/
@Data
public class PromSeries {
/**
* 状态
* 成功-- success
*/
private String status;
/**
* 时序数据列表
*/
private List<LinkedHashMap<String, Object>> data;
}
4.2、时序数据接口实现
/**
* 指标查询接口类
*
* @author 星空流年
* @date 2023/7/10
*/
public interface PromQueryService {
/**
* 获取时序数据
*
* @param start 开始时间戳, 单位:秒
* @param end 结束时间戳, 单位:秒
* @param match 查询指标
* @return {@code List<LinkedHashMap<String, Object>>}
*/
List<LinkedHashMap<String, Object>> getSeriesList(String start, String end, String match);
}
根据标签匹配器获取时序数据接口实现类:PromQueryServiceImpl
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import xxx.entity.pojo.PromQueryData;
import xxx.entity.pojo.PromQueryResponse;
import xxx.service.PromQueryService;
import xxx.util.RestTemplateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 指标查询接口实现类
*
* @author 星空流年
* @date 2023/7/10
*/
@Slf4j
@Service("promQueryService")
public class PromQueryServiceImpl implements PromQueryService {
@Resource
private RestTemplateUtils restTemplateUtils;
@Override
public List<LinkedHashMap<String, Object>> getSeriesList(String start, String end, String match, Integer datasource) {
JSONObject param = new JSONObject();
param.put("start", start);
param.put("end", end);
param.put("match[]", match);
// prometheus的URL连接地址, 根据需要修改
String url = "http://localhost:9090" + "/api/v1/series";
return getSeriesDataList(url, param);
}
/**
* 获取时序数据列表
*
* @param promUrl 时序URL
* @param param 请求参数
* @return 时序数据列表
*/
private List<LinkedHashMap<String, Object>> getSeriesDataList(String promUrl, JSONObject param) {
String http = getHttp(promUrl, param);
PromSeries promSeries = JSON.parseObject(http, PromSeries.class);
if (Objects.nonNull(promSeries)) {
String status = promSeries.getStatus();
if (StringUtils.isBlank(status) || !StringUtils.equals(PromConstants.SUCCESS, status)) {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
return promSeries.getData();
}
/**
* 获取http连接
*
* @param promUrl 连接URL
* @param param 请求参数
* @return http连接
*/
private String getHttp(String promUrl, JSONObject param) {
String http = null;
try {
http = restTemplateUtils.getHttp(promUrl, param);
} catch (Exception e) {
log.error("请求地址: {}, 请求参数: {}, 异常信息: {}", promUrl, param, Throwables.getStackTraceAsString(e));
}
return http;
}
}
**************************************************** 林深时见鹿,海蓝时见鲸 ****************************************************