Micrometer 为 Java 平台上的性能数据收集提供了一个通用的 API,它提供了多种度量指标类型(Timers、Guauges、Counters等),同时支持接入不同的监控系统,例如 Influxdb、Graphite、Prometheus 等。我们可以通过 Micrometer 收集 Java 性能数据,配合 Prometheus 监控系统实时获取数据,并最终在 Grafana 上展示出来,从而很容易实现应用的监控。
Micrometer 中有两个最核心的概念,分别是计量器(Meter)和计量器注册表(MeterRegistry)。计量器用来收集不同类型的性能指标信息,Micrometer 提供了如下几种不同类型的计量器:
本次演示环境,我是在本机 MAC OS 上操作,以下是安装的软件及版本:
注意:这里为了快速方便启动 Prometheus、Grafana 服务,我使用 Docker 方式启动,所以本机需要安装好 Docker 环境,这里忽略 Docker 的安装过程,着重介绍一下 Spring Boot 项目如何使用 Micrometer 来监控 Java 应用性能,并集成到 Prometheus 最终使用 Grafana Dashboard 展示出来。
我们一般说 Spring Boot 集成 Micrometer 值得时 Spring 2.x 版本,因为在该版本 spring-boot-actuator
使用了 Micrometer 来实现监控,而在 Spring Boot 1.5x 中可以通过micrometer-spring-legacy
来使用 micrometer,显然在 2.x 版本有更高的集成度,使用起来也非常方便了。那么创建一个 Spring Boot 2.1.4.RELEASE 版本的工程 springboot2-promethues
,首先添加依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.1.4</version>
</dependency>
这里引入了 io.micrometer
的 micrometer-registry-prometheus
依赖以及 spring-boot-starter-actuator
依赖,因为该包对 Prometheus 进行了封装,可以很方便的集成到 Spring Boot 工程中。
其次在 application.properties
中配置如下:
server.port=8088
spring.application.name=springboot2-prometheus
management.endpoints.web.exposure.include=*
management.metrics.tags.application=${spring.application.name}
这里 management.endpoints.web.exposure.include=*
配置为开启 Actuator 服务,因为Spring Boot Actuator 会自动配置一个 URL 为 /actuator/Prometheus
的 HTTP 服务来供 Prometheus 抓取数据,不过默认该服务是关闭的,该配置将打开所有的 Actuator 服务。management.metrics.tags.application
配置会将该工程应用名称添加到计量器注册表的 tag 中去,方便后边 Prometheus 根据应用名称来区分不同的服务。
然后在工程启动主类中添加 Bean 如下来监控 JVM 性能指标信息:
@SpringBootApplication
public class Springboot2PrometheusApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot2PrometheusApplication.class, args);
}
@Bean
MeterRegistryCustomizer<MeterRegistry> configurer(@Value("${spring.application.name}") String applicationName){
return registry -> registry.config().commonTags("application", applicationName);
}
}
最后,启动服务,浏览器访问 http://127.0.0.1:8088/actuator/prometheus
就可以看到应用的 一系列不同类型 metrics 信息,例如 http_server_requests_seconds summary
、jvm_memory_used_bytes gauge
、jvm_gc_memory_promoted_bytes_total counter
等等。
Prometheus 的安装配置可参考之前文章 Prometheus 监控报警系统 AlertManager 之邮件告警,写的很详细,这里就不在详细说明了,修改 prometheus.yml
配置,在上篇文章配置示例基础上,添加上边启动的服务地址来执行监控。
$ vim prommetheus.yml
......
- job_name: 'application'
scrape_interval: 5s
metrics_path: '/actuator/prometheus'
file_sd_configs:
- files: ['/usr/local/prometheus/groups/applicationgroups/*.json']
这里依然采用 file_sd_configs
方式动态服务发现,新建 <local_dir>/groups/applicationgroups/application.json
文件如下:
$ vim groups/applicationgroups/application.json
[
{
"targets": [
"192.168.1.124:8088"
],
"labels": {
"instance": "springboot2-prometheus",
"service": "springboot2-prometheus-service"
}
}
]
这里 192.168.1.124:8088
就是上边本地启动的服务地址,也就是 Prometheus 要监控的服务地址,同时添加一些与应用相关的标签,方便后期执行 PromSQL 查询语句区分。最后重启 Prometheus 服务,查看 Prometheus UI 界面确认 Target 是否添加成功。
在 Graph 页面执行一个简单的查询,也是获取 springboot2-prometheus
服务的 JVM 性能指标值的。
Prometheus 现在已经可以正常监控到应用 JVM 信息了,那么我们可以配置 Grafana Dashboard 来优雅直观的展示出来这些监控值了。首先创建 Grafana 服务,这里为了方便,依旧采用 Docker 的方式启动,启动命令如下:
$ docker run -d -p 3000:3000 --name=grafana grafana/grafana
启动完毕后,浏览器访问 http://192.168.1.121:3000
即可,首次登录使用 admin:admin
默认账户密码登录并修改密码。登录完毕,需要添加数据源,这里我们要添加的就是上边 Prometheus 数据源,配置如下图:
配置完毕,接下来需要导入对应的监控 JVM 的 Dashboard 模板,模板编号为 4701
。
导入完毕后,就可以看到 JVM (Micrometer)
各种类型指标监控图形化以后的页面。
上边是 spring-boot-actuator
集成了 Micrometer 来提供的默认监控项,覆盖 JVM 各个层间的监控,配合 Grafana Dashboard 模板基本可以满足我们日常对 Java 应用的监控。当然,它也支持自定义监控指标,实现各个方面的监控,例如统计访问某一个 API 接口的请求数,统计实时在线人数、统计实时接口响应时间等功能,而这些都可以通过使用上边的四种计量器来实现。接下来,来演示下如何自定义监控指标并展示到 Grafana 上。
我们继续在 springboot2-promethues
工程上添加 IndexController.java
,来实现分别统计访问 index 及 core 接口请求次数,代码如下:
@RestController
@RequestMapping("/v1")
public class IndexController {
@Autowired
MeterRegistry registry;
private Counter counter_core;
private Counter counter_index;
@PostConstruct
private void init(){
counter_core = registry.counter("app_requests_method_count", "method", "IndexController.core");
counter_index = registry.counter("app_requests_method_count", "method", "IndexController.index");
}
@RequestMapping(value = "/index")
public Object index(){
try{
counter_index.increment();
} catch (Exception e) {
return e;
}
return counter_index.count() + " index of springboot2-prometheus.";
}
@RequestMapping(value = "/core")
public Object coreUrl(){
try{
counter_core.increment();
} catch (Exception e) {
return e;
}
return counter_core.count() + " coreUrl Monitor by Prometheus.";
}
}
说明一下,这里是一个简单的 RestController 接口,使用了 Counter 计量器来统计访问 /v1/index
及 /v1/core
接口访问量。因为访问数会持续的增加,所以这里使用 Counter 比较合适。启动服务,我们来分别访问一下这两个接口,为了更好的配合下边演示,可以多访问几次。
服务可以正常访问,并且访问了 6 次 /v1/index
,访问了 10 次 /v1/core
。接下来,我们可以到 Prometheus UI 界面上使用 PromSQL 查询自定义的监控信息了。分别添加 Graph 并执行如下查询语句,查询结果如下:
可以看到正确统计出来这两个接口请求的访问数,这里解释一下查询语句:app_requests_method_count_total{application="springboot2-prometheus", instance="springboot2-prometheus", method="IndexController.core"}
这里的
app_requests_method_count_total
为上边代码中设置的 Counter 名称。application
为初始化 registry 时设置的通用标签,标注应用名称,这样做好处就是可以根据应用名称区分不同的应用。instance
为 <local_dir>/groups/applicationgroups/application.json
中配置的 instance 实例名称,用来区分应用实例。method
为上边代码中设置的 Counter 标签名称,可以用来区分不同的方法,这样就不用为每一个方法设置一个 Counter 了。接下来,我们在 Grafana Dashboard 上添加一个新的 Panel 并添加 Query 查询,最后图形化展示出来。首先添加一个 Panel 并命名为 自定义监控指标
,然后点击 Add Query
增加一个新的 Query 查询,查询语句为上边的 PromSQL 语句,不过这里为了更好的扩展性,我们可以将 application
及 instance
两个参数赋值为变量,而这些变量可以直接从 Prometheus 上传递过来,最终的查询语句为 app_requests_method_count_total{application="$application", instance="$instance", method="IndexController.core"}
,最后修改 Title 为 实时访问量 /v1/core
,保存一下,返回首页就可以看到刚添加的 Dashboard 了,是不是很直观。
上边针对某个或某几个接口请求次数做了监控,如果我们想针对整个应用监控所有接口请求总次数,这个该如何实现呢?监控请求次数可以继续使用 Counter 计数器,整个应用所有请求,我们自然而然的想到了 Spring AOP,通过切面注入可以做到统计所有请求记录,添加依赖如下:
...
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
添加 AspectAop.java
代码到 Springboot2PrometheusApplication.java
同级目录, 代码如下:
@Component
@Aspect
public class AspectAop {
@Autowired
MeterRegistry registry;
private Counter counter_total;
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(public * com.promethues.demo.controller.*.*(..))")
private void pointCut(){}
@PostConstruct
public void init(){
counter_total = registry.counter("app_requests_count", "v1", "core");
}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint)throws Throwable {
startTime.set(System.currentTimeMillis());
counter_total.increment();
}
@AfterReturning(returning = "returnVal", pointcut = "pointCut()")
public void doAftereReturning(Object returnVal){
System.out.println("请求执行时间:" + (System.currentTimeMillis() - startTime.get()));
}
}
这里 Spring AOP 操作代码就不在说了,我们创建了一个名称为 app_requests_count
的 Counter,所有请求过来都会执行 counter_total.increment();
操作,从而实现统计所有请求总数。重启服务,访问多次不同的接口,然后在 Prometheus UI 界面执行 PromSQL 查询,查询语句为 app_requests_count_total{application="springboot2-prometheus", instance="springboot2-prometheus", v1="core"}
查询结果如下:
可以看到,能够正确统计出来所有的请求数量,现在,我们可以在 Grafana 上之前的面板上增加一个新的 Query 并图形化显示出来了,Query 语句为: app_requests_count_total{application="$application", instance="$instance",v1="core"}
, 添加完成后,展示效果如下:
接下来,来演示下如何监控瞬时数据变化,例如实时交易总金额,实时网络请求响应时间,实时在线人数等,这里我们简单模拟一下实时在线人数监控,这里采用 Gauge 计量仪来做为指标统计类型,在 IndexController.java
中添加相关代码如下:
@RestController
@RequestMapping("/v1")
public class IndexController {
@Autowired
MeterRegistry registry;
private Counter counter_core;
private Counter counter_index;
private AtomicInteger app_online_count;
@PostConstruct
private void init(){
counter_core = registry.counter("app_requests_method_count", "method", "IndexController.core");
counter_index = registry.counter("app_requests_method_count", "method", "IndexController.index");
app_online_count = registry.gauge("app_online_count", new AtomicInteger(0));
}
@RequestMapping(value = "/index")
public Object index(){
try{
counter_index.increment();
} catch (Exception e) {
return e;
}
return counter_index.count() + " index of springboot2-prometheus.";
}
@RequestMapping(value = "/core")
public Object coreUrl(){
try{
counter_core.increment();
} catch (Exception e) {
return e;
}
return counter_core.count() + " coreUrl Monitor by Prometheus.";
}
@RequestMapping(value = "/online")
public Object onlineCount(){
int people = 0;
try {
people = new Random().nextInt(2000);
app_online_count.set(people);
} catch (Exception e){
return e;
}
return "current online people: " + people;
}
}
重启服务,访问一下 /v1/online
接口,得到一个 2000
以内的随机数作为实时在线人数,浏览器访问一下,得到结果如下:
我们在 Prometheus UI 界面执行一下 PromeSQL 查询语句 app_online_count{application="springboot2-prometheus", instance="springboot2-prometheus"}
,同样能够对应获取到实时数据。
继续在 Grafana 上之前的面板上增加一个新的 Query 并图形化显示出来,Query 语句为: app_online_count{application="$application", instance="$instance"}
, 添加完成后,展示效果如下:
注意:这里我采用了 Grafana 中 Gauge 图形来展示的,可以根据实际要求来展示对应的数据。
参考资料