如何为你的Spring Boot应用装上一个功能强大的监控仪表盘
在现代微服务架构中,系统监控已成为保障应用稳定性的关键环节。通过有效的监控,我们可以实时了解应用的运行状态,及时发现并解决性能问题。本文将介绍如何使用Micrometer及其注册表(Registry)在Spring Boot环境中实现全面系统参数监控。
1 Micrometer简介
Micrometer是一款供应商中立的应用程序指标门面(Facade),类似于SLF4J在日志领域的作用,它为不同监控系统提供了统一的度量采集API。它可以与多种监控系统(如Prometheus、Datadog、New Relic、InfluxDB等)无缝集成,让你无需修改代码即可切换监控后端。
Micrometer的架构围绕三个核心概念构建:
- Meter:表示具体的度量指标,如计数器(Counter)、计时器(Timer)等
- MeterRegistry:负责创建和存储Meter的核心接口
- Binder:将框架内部指标(如JVM指标)自动绑定到注册表
2 Spring Boot集成Micrometer
2.1 添加依赖
首先,在您的pom.xml
中添加以下依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<!-- 使用Prometheus作为监控系统 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
如果使用Gradle,可以在build.gradle
中添加:
arduino
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-core'
implementation 'io.micrometer:micrometer-registry-prometheus'
2.2 配置监控端点
在application.yml
或application.properties
中进行配置:
yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
region: north
export:
prometheus:
enabled: true
此配置开启了Actuator的监控端点,并设置了全局标签(application和region),这些标签会附加到所有监控指标上
2.3 选择监控系统
Micrometer支持多种监控系统,以下是常见选择:
监控系统 | 适用场景 | 特点 |
---|---|---|
Prometheus | 时间序列监控 | 开源、拉取模式、适合Kubernetes环境 |
InfluxDB | 处理大量时间序列数据 | 高性能、支持类SQL查询 |
Datadog | 全栈可观测性 | 商业化、功能全面、支持多种数据源 |
StatsD | 简单指标收集 | 轻量级、推送模式、易于部署 |
3 使用MeterRegistry记录指标
Spring Boot会自动配置一个MeterRegistry
实例,可以直接在代码中使用它来记录各种指标。
3.1 计数器(Counter)
计数器用于记录单调递增的指标,如请求总数、订单创建数量等。
java
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final Counter orderCounter;
public OrderService(MeterRegistry meterRegistry) {
this.orderCounter = meterRegistry.counter("orders.total",
"type", "created");
}
public void createOrder() {
// 业务逻辑
orderCounter.increment();
}
}
3.2 计量仪(Gauge)
Gauge用于测量瞬时值,如内存使用量、缓存大小等。
java
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class MyGaugeComponent {
private AtomicInteger myValue = new AtomicInteger(0);
private final MeterRegistry meterRegistry;
public MyGaugeComponent(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@PostConstruct
public void init() {
Gauge.builder("my_custom_gauge", myValue, AtomicInteger::get)
.description("A custom gauge example")
.tags("component", "gauge")
.register(meterRegistry);
}
public void updateValue(int newValue) {
myValue.set(newValue);
}
@Bean
public CommandLineRunner bindThreadPoolToMetrics(
MeterRegistry registry,
@Qualifier("asyncExecutorService") ThreadPoolTaskExecutor executor,
SystemStatusService systemStatusService) {
Gauge.builder("system.status.code", systemStatusService::getStatusCode)
.description("当前系统状态码")
.register(registry);
return args -> {
registry.gauge("asyncExecutorService.threadpool.core.size", executor, ThreadPoolTaskExecutor::getCorePoolSize);
registry.gauge("asyncExecutorService.threadpool.max.size", executor, ThreadPoolTaskExecutor::getMaxPoolSize);
registry.gauge("asyncExecutorService.threadpool.pool.size", executor, ThreadPoolTaskExecutor::getPoolSize);
registry.gauge("asyncExecutorService.threadpool.active.count", executor, ThreadPoolTaskExecutor::getActiveCount);
registry.gauge("asyncExecutorService.threadpool.queue.size", executor,
new ToDoubleFunction<ThreadPoolTaskExecutor>() {
@Override
public double applyAsDouble(ThreadPoolTaskExecutor value) {
return value.getThreadPoolExecutor().getQueue().size();
}
});
//registry.gauge("system.status.code", systemStatusService, SystemStatusService::getStatusCode);
};
}
@Bean(name = "threadPoolTaskScheduler")
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(2);
executor.setThreadNamePrefix("TST-SCHEDULER");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(10);
return executor;
}
}
3.3 计时器(Timer)
计时器用于测量短时任务的持续时间,内置百分位计算。
kotlin
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class ApiService {
private final Timer apiTimer;
public ApiService(MeterRegistry meterRegistry) {
this.apiTimer = meterRegistry.timer("api.requests",
"api_type", "external");
}
public String callExternalApi() {
return apiTimer.record(() -> {
// 模拟API调用
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "API response";
});
}
}
3.4 分布摘要(DistributionSummary)
分布摘要用于记录值的分布情况,适用于不涉及时间的测量,如响应体大小。
arduino
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class ResponseService {
private final DistributionSummary responseSizeSummary;
public ResponseService(MeterRegistry meterRegistry) {
this.responseSizeSummary = DistributionSummary
.builder("response.size")
.description("Response size distribution")
.baseUnit("bytes")
.register(meterRegistry);
}
public void processResponse(String response) {
// 记录响应大小
responseSizeSummary.record(response.getBytes().length);
}
}
4 监控HTTP请求
Spring Boot会自动监控HTTP请求,并提供一些默认的Metrics,如请求数量、响应时间等。您可以通过以下配置自定义这些指标:
yaml
management:
metrics:
web:
server:
request:
autotime:
enabled: true
metric:
name: http.server.requests
还可以通过自定义过滤器来增强HTTP监控:
java
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class MetricFilter extends OncePerRequestFilter {
private final MeterRegistry meterRegistry;
public MetricFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
String method = request.getMethod();
meterRegistry.counter("http.requests.total",
"method", method,
"path", path)
.increment();
long start = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - start;
meterRegistry.timer("http.requests.duration",
"method", method,
"path", path,
"status", String.valueOf(response.getStatus()))
.record(duration, TimeUnit.MILLISECONDS);
}
}
}
5 自定义MeterFilter
MeterFilter允许您修改或过滤Metrics。例如,可以重命名Metrics,或删除一些不必要的Metrics。
kotlin
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MicrometerConfig {
@Bean
public MeterFilter renameFilter() {
return MeterFilter.renameTag("http.server.requests",
"method",
"http_method");
}
@Bean
public MeterFilter ignoreTagFilter() {
return MeterFilter.ignoreTag("uri");
}
@Bean
public MeterFilter denyFilter() {
return MeterFilter.deny(id -> {
String meterName = id.getName();
// 拒绝以"temp"开头的指标
return meterName != null && meterName.startsWith("temp");
});
}
}
6 查看监控数据
启动Spring Boot应用后,您可以访问/actuator/prometheus
端点查看Prometheus格式的监控数据。
示例输出:
ini
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{application="my-app",area="heap",id="Eden Space",} 1.2632928E7
7 常见问题与解决方案
- Metric名称冲突
如果应用中使用了多个库,它们都使用了相同的Metric名称,可能会导致冲突。解决这个问题的方法是使用不同的标签来区分这些Metrics。 - 性能问题
如果应用需要记录大量的Metrics,可能会导致性能问题。解决这个问题的方法是减少Metrics的数量,或者使用更高效的监控系统。 - 数据丢失
如果监控系统出现故障,可能会导致数据丢失。解决这个问题的方法是使用高可用的监控系统,并定期备份数据。 - 指标基数爆炸
避免使用高基数的标签(如用户ID),这会导致指标数量急剧增加,影响监控系统性能。
8 最佳实践
- 标签设计:使用有限且一致的标签值集合,避免高基数标签。
- 命名规范:使用"."分隔小写单词字符,Micrometer会自动转换为各监控系统适应的格式。
- 监控策略:只监控关键指标,避免过度监控导致系统负载过重。
- 异常处理:确保指标记录不会影响主要业务逻辑,妥善处理异常。
- 文档化:为自定义指标提供清晰的文档说明,包括指标名称、标签含义和预期值范围。
9 总结
通过Spring Boot集成Micrometer,我们可以为应用快速添加一个功能强大的"监控仪表盘",实时了解应用的各项指标,如CPU使用率、内存占用、请求响应时间等。Micrometer作为监控门面,让我们能够灵活选择监控后端,而无需修改代码。
关键要点:
- Micrometer提供与供应商无关的监控指标接口
- Spring Boot自动配置简化了集成过程
- 合理使用标签和命名规范避免常见问题
- 结合Grafana等可视化工具可以更好地展示监控数据
通过本文介绍的方法,我们可以快速为Spring Boot应用添加监控功能,及时发现并解决性能问题,确保应用的稳定性和可靠性。
彩蛋
监控参数使用prometheus+grafana展示出来,如下图所示:
