常规前置操作:看文档写demo
开整,首先引入依赖,将actuator的数据转换成Prometheus需要的格式
<!-- actuator begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--集成prometheus客户端,将actuator监控的指标转为prometheus格式-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- actuator end -->
docker拉取prometheus镜像并安装参考
https://blog.csdn.net/qq_26462567/article/details/108501175
我的配置
#普罗米修斯docker配置
sudo docker run -d -p 9090:9090 --name prom -v /data/prometheus/data/prometheus.yml:/etc/prometheus/prometheus.yml bitnami/prometheus
#这里注意普罗米修斯配置文件在容器的/etc/prometheus/prometheus.yml下,做数据卷映射的时候要匹配,或者可以用--config指定配置文件
prometheus的配置文件如下prometheus.yml,采集路径配置的是actuator暴露的prometheus端点地址。
scrape_configs:
# 任意写,建议英文,不要包含特殊字符
- job_name: 'test'
# 采集的间隔时间
scrape_interval: 15s
# 采集时的超时时间
scrape_timeout: 10s
# 采集路径
metrics_path: '/actuator/prometheus'
# 采集服务的地址,也就是我们应用的地址
static_configs:
- targets: [192.168.206.1:9011']
整完以上内容先访问要监控的应用的端点信息http://ip:port/actuator/prometheus
看似没有问题!!!!!!
第一坑 spring actuator 整合Prometheus报错: “INVALID” “”" is not a valid start token
然后打开http://192.168.206.129:9090/targets普罗米修斯监控页面报错"INVALID" “”" is not a valid start token
这是神马情况?查看报错信息 :" 不是有效的开头,查找了半天发现是消息格式不对。为什么不对呢。再找了半天,发现项目配置了json序列化
@Configuration
public class HttpConverterConfig {
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullListAsEmpty);
//解决中文乱码问题:在方法内部添加这段代码
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastMediaTypes.add(MediaType.ALL);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
fastConverter.setFastJsonConfig(fastJsonConfig);
return new HttpMessageConverters(fastConverter);
}
}
导致返回的json被再一次编码,所以报错。把序列化去掉后正确的消息格式`
第二坑 关于micrometer的方法监控切面TimedAspect和@Time注解了方法,访问http://ip:port/actuator/metrics 却找不到相应的metrics问题
actuator集成了micrometer,micrometer提供了方法级的监控,只需要在方法上注解@Timed(“sample.controller”),sample.controller就是metrics的标签,
@RestController
@RequestMapping(path = "/sample")
public class SampleController {
private final SampleService sampleService;
public SampleController(SampleService sampleService) {
this.sampleService = sampleService;
}
@Timed("sample.controller")
@GetMapping("/call-rest-template")
public Map<String, Object> callRestTemplate() {
return this.sampleService.callRestTemplate();
}
}
然后将TimedAspect 切面注入容器
@Configuration
@EnableAspectJAutoProxy
public class MetricsConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
接着访问http://ip:port/actuator/metrics,????找不到相应的metrics
{
"names": [
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.pause",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
}
分析TimedAspect源码发现,在进入切面后调用processWithTimer,processWithTimer调用record,此时才进行metrics的注册。得出原因,需要访问一次@Time注解了的方法才能将这个指标注册。
@Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
//............省略一万行
if (!timed.longTask()) {
return processWithTimer(pjp, timed, metricName, stopWhenCompleted);
}
//............省略一万行
}
private Object processWithTimer(ProceedingJoinPoint pjp, Timed timed, String metricName, boolean stopWhenCompleted) throws Throwable {
//............省略一万行
if (stopWhenCompleted) {
try {
return ((CompletionStage<?>) pjp.proceed()).whenComplete((result, throwable) ->
record(pjp, timed, metricName, sample, getExceptionTag(throwable)));
}
//............省略一万行
}
private void record(ProceedingJoinPoint pjp, Timed timed, String metricName, Timer.Sample sample, String exceptionClass) {
try {
sample.stop(Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles())
.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
访问测试接口http://localhost:8080/sample/call-rest-template后再次调用http://localhost:8080/actuator/metrics
{
"names": [
"http.client.requests",
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.pause",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.start.time",
"process.uptime",
"sample.controller", <-------------找到sample.controller
"system.cpu.count",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
}
访问http://localhost:8080/actuator/metrics/sample.controller
{
"name" : "sample.controller",
"description" : null,
"baseUnit" : "seconds",
"measurements" : [ {
"statistic" : "COUNT",
"value" : 2.0
}, {
"statistic" : "TOTAL_TIME",
"value" : 2.4237243
}, {
"statistic" : "MAX",
"value" : 0.0
} ],
"availableTags" : [ {
"tag" : "exception",
"values" : [ "None", "none" ]
}, {
"tag" : "method",
"values" : [ "callRestTemplate", "GET" ]
}, {
"tag" : "uri",
"values" : [ "/sample/call-rest-template" ]
}, {
"tag" : "class",
"values" : [ "com.izeye.sample.web.SampleController" ]
}, {
"tag" : "outcome",
"values" : [ "SUCCESS" ]
}, {
"tag" : "status",
"values" : [ "200" ]
} ]
}
后续改造自动注册metrics