springboot2项目集成prometheus说明
1、pom文件引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_common</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_hotspot</artifactId>
<version>0.6.0</version>
</dependency>
2、application.properties添加暴露采集点信息
management.metrics.distribution.percentiles-histogram.http=true
management.endpoints.web.base-path=/actuator
management.endpoints.web.exposure.include=prometheus,metrics
management.metrics.export.prometheus.enabled=true
management.metrics.export.prometheus.descriptions=true
management.metrics.export.prometheus.step=1m
management.metrics.web.server.auto-time-requests=true
3、创建Configuration类来注入需要metrics对象
@Configuration
public class PrometheusBeanConfig {
/**
* http请求总数量Counter
* @return Counter对象
*/
@Bean
public io.prometheus.client.Counter appHttpRequestTotalCountCollector(){
return io.prometheus.client.Counter.build()
.name("app_request_total")
.labelNames("appId","businessType","method","returnCode")
.help("init app_request_total").create()
.register();
}
@Bean
public io.prometheus.client.Histogram requestsLatencySeconds(){
return io.prometheus.client.Histogram.build()
.name("requests_latency_seconds")
.labelNames("appId","businessType","method")
.help("init requests_latency_seconds")
.create()
.register();
}
/**
* 密码资产接口指标
* @return Gauge对象
*/
@Bean
public io.prometheus.client.Gauge cipherAssetsInventoryGaugeCollector(){
return io.prometheus.client.Gauge.build()
.name("cipher_assets_inventory")
.labelNames("type")
.help("init cipher_assets_inventory")
.create()
.register();
}
}
4、创建拦截器或者aop进行metric埋点,根据具体需要自行选择埋点方式(埋点部分)
(1)、使用aop统一拦截处理埋点
@Autowired
private Gauge gauge;
@Autowired
private Counter counter;
@Autowired
private Histogram histogram;
private Histogram.Timer histogramRequestTimer;
@Before("execution(public * com.infosec.cssp.nettsa.controller.NetTsaController.*(..))")
public void doBefore(JoinPoint joinPoint) {
Object args[] = joinPoint.getArgs();
JSONObject requestJson = (JSONObject) args[1];
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String appId = request.getHeader("appId");
String method = requestJson.getJSONObject(FieldNameConstant.FIELD_MSG_HEADER).getString(FieldNameConstant.FIELD_BUSINESS_TYPE);
// http响应时间埋点
histogramRequestTimer = histogram.labels(appId, BUSSINESS_TYPE, method).startTimer();
}
@AfterReturning(pointcut = "execution(public * com.infosec.cssp.nettsa.controller.NetTsaController.*(..))",returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result){
Object args[] = joinPoint.getArgs();
String requestParam = args[1].toString();
JSONObject requestJson = JSONObject.parseObject(requestParam);
JSONObject responseJson = (JSONObject) JSON.toJSON(result);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String appId = request.getHeader("appId");
rmp.setAppId(appId);
//TODO appName需要从接口中取
rmp.setAppName(AppType.getName(appId));
String returnCode = responseJson.getJSONObject(Constants.HEAD).getString(Constants.CODE);
String method = requestJson.getJSONObject(FieldNameConstant.FIELD_MSG_HEADER)
.getString(FieldNameConstant.FIELD_BUSINESS_TYPE);
String success = "0";
String error = "-1";
if (success.contentEquals(returnCode) && method != null && CommonConstant.GENERATE_TIME_STAMP.equals(method)) {
gauge.labels(BUSSINESS_TYPE).inc();
}
// http请求量counter埋点
counter.labels(appId, BUSSINESS_TYPE, method, success.equals(returnCode) ? returnCode : error).inc();
histogramRequestTimer.observeDuration();
}
注:这种方法经过压测时发现会导致得到的平均响应时间出现问题,因为并发导致,所以改为Around方式,且需要调用Histogram的time方法(解决多线程问题),比如
//写histogram类型指标信息
String finalMethodName = methodName;
Object result = histogram.labels(appId, CommonConstant.BUSINESS_TYPE, methodName).time(new Callable<Object>() {
@Override
public Object call() {
try {
//注意,如果调用joinPoint.proceed()方法,则修改的参数值不会生效,必须调用joinPoint.proceed(Object[] args),
// 此时才真的去调业务方法,调用顺序为:around->before->method->after
return joinPoint.proceed(args);
} catch (Throwable throwable) {
logger.error("调用方法{}出错,{}", finalMethodName, throwable);
return new ResultDto(errorCode, "调用方法" + finalMethodName + "出错:" + throwable.getMessage(), null);
}
}
});
(2)、采用自定义注解方式实现埋点。可以对特定的方法进行埋点处理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface PrometheusAopLog {
}
@Aspect
@Component
public class PrometheusLogAspect {
private static Logger logger = LoggerFactory.getLogger(PrometheusLogAspect.class);
@Autowired
private Counter counter;
@Autowired
private Histogram histogram;
/**
* 只拦截特定的注解
*/
@Pointcut("@annotation(com.xxx.yyy.annotation.PrometheusAopLog)")
public void pointCut() {
}
@Around(value = "pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
ResultDto dto = new ResultDto();
String errorCode = "-1";
String errorMsg = "unknow error";
String methodName = null;
try {
//获取方法参数值数组
Object[] args = joinPoint.getArgs();
methodName = joinPoint.getSignature().getName();
if (!StringUtils.isEmpty(RequestContextHolder.getRequestAttributes())) {
String finalMethodName = methodName;
//写histogram类型指标信息,用于统计接口响应耗时
Object result = histogram.labels(PrometheusConstant.SERVER_TYPE, methodName).time(() -> {
try {
return joinPoint.proceed(args);
} catch (Throwable throwable) {
logger.error("调用方法{}出错,{}", finalMethodName, throwable);
dto.setResultDto("-1", "调用方法【" + finalMethodName + "】出错", null);
return dto;
}
});
ResponseCraMa tmp = (ResponseCraMa) result;
errorCode = tmp.getResponse().getHeader().getErrnum();
//写counter指标
counter.labels(PrometheusConstant.SERVER_TYPE, methodName, "0".equals(errorCode) ? "0" : "-1").inc();
//返回方法最终处理结果
return result;
}
} catch (Exception e) {
logger.error("aop around error:{}", e);
errorMsg = "aop调用方法【" + methodName + "】出错";
}
dto.setResultDto(errorCode, errorMsg, null);
return dto;
}
}
使用时,直接在Controller层的方法上使用@PrometheusAopLog 注解即可。
5、自定义采集请求url,供prometheus服务调用(采集部分)
@RestController
@CrossOrigin
@RequestMapping("monitor")
public class MonitorController {
@RequestMapping(value = "/prometheus",produces = {"application/json;charset=UTF-8"})
public void exportMetrics(HttpServletResponse response) {
StringWriter writer = new StringWriter();
try {
TextFormat.write004(writer, CollectorRegistry.defaultRegistry.metricFamilySamples());
} catch (IOException var3) {
throw new RuntimeException(var3);
}
String response2 = writer.toString();
try (OutputStream os = response.getOutputStream()) {
os.write(response2.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
6、验证是否埋点成功
- 启动项目
- 确定prometheus服务成功连接到项目
可以到prometheus管理界面的Status
菜单的Targets
下看当期项目的状态是UP
还是DOWN
如果是DOWN
,则说明项目提供的请求url不正确。 - 访问项目某个接口
- 到prometheus提供的操作界面中访问埋点数据
比如埋点指标为:app_request_total
,在prometheus管理界面的Graph
菜单下输入该指标后点击Execute
,
查看返回数据.(注意:不同指标,prometheus会生成不同指标名称)