Spring Cloud 微服务监控全链路——Micrometer + Prometheus + Grafana 实战
Spring Cloud 微服务监控全链路——Micrometer + Prometheus + Grafana 实战
适读人群:负责微服务系统稳定性、正在建设可观测性体系的后端工程师和 SRE | 阅读时长:约18分钟 | 核心价值:从 0 到 1 搭建完整的微服务监控体系,建立从指标采集到告警的全链路可观测性
"我们不知道系统出问题了"
2022年,我做过一次事后复盘,一个做 SaaS 的团队。他们的系统在某个周五下午出现了严重的性能问题,用户反馈接口超时。但从接到第一个用户投诉,到定位到根因,花了整整 3 小时。
"为什么这么慢?"我问。
"因为我们完全靠用户投诉才知道出问题了。系统里没有监控告警,不知道哪个服务挂了,只能一个个排查。"
这不是个例。很多团队把大量精力放在功能开发上,监控体系建设落后,出了问题只能"人肉排查"。
今天,我们把 Micrometer + Prometheus + Grafana 这套微服务监控标准方案,从接入到告警完整过一遍。
一、可观测性的三大支柱
在讲具体工具之前,先明确一个框架:现代系统的可观测性(Observability)由三大支柱组成:
- Metrics(指标):数字型的时序数据,如 QPS、响应时间、错误率、CPU 使用率
- Logs(日志):离散的事件记录,如请求日志、异常堆栈
- Traces(链路追踪):跨服务调用的完整调用链
今天主要讲 Metrics 这一支柱(Micrometer + Prometheus + Grafana),另外两个支柱(ELK 日志、SkyWalking/Zipkin 链路追踪)是后续的话题。
二、Micrometer 接入:指标采集的标准方式
Micrometer 是 Spring Boot Actuator 2.x 内置的 Metrics Facade,就像 SLF4J 对于日志一样,它提供统一的 Metrics API,底层可以接入 Prometheus、InfluxDB 等不同的监控系统。
2.1 依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics # 暴露 Prometheus 端点
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name} # 所有指标携带应用名标签,Grafana 过滤时用
env: ${ENV:dev} # 携带环境标签访问 http://localhost:8080/actuator/prometheus,能看到大量 JVM、HTTP、数据库连接池等自动采集的指标。
2.2 自定义业务指标
Spring Boot 自动采集的指标覆盖了基础设施层,但业务层的指标(如订单创建成功率、支付转化率)需要手动埋点:
@Service
public class OrderServiceWithMetrics {
private static final Logger log = LoggerFactory.getLogger(OrderServiceWithMetrics.class);
// 计数器:统计订单创建次数(按状态分类)
private final Counter orderCreatedCounter;
private final Counter orderFailedCounter;
// 计时器:统计订单创建耗时
private final Timer orderCreateTimer;
// 仪表盘:统计当前待处理订单数(实时值)
private final AtomicInteger pendingOrderCount = new AtomicInteger(0);
// 分布摘要:统计订单金额分布
private final DistributionSummary orderAmountSummary;
public OrderServiceWithMetrics(MeterRegistry registry) {
// 使用 tags 对指标进行多维度标记
this.orderCreatedCounter = Counter.builder("order.create.total")
.description("订单创建总次数")
.tag("result", "success")
.register(registry);
this.orderFailedCounter = Counter.builder("order.create.total")
.description("订单创建总次数")
.tag("result", "failure")
.register(registry);
this.orderCreateTimer = Timer.builder("order.create.duration")
.description("订单创建耗时")
.publishPercentiles(0.5, 0.75, 0.95, 0.99) // 发布 P50/P75/P95/P99
.publishPercentileHistogram() // 发布直方图(用于计算 SLA)
.register(registry);
// Gauge:注册一个实时值
Gauge.builder("order.pending.count", pendingOrderCount, AtomicInteger::get)
.description("当前待处理订单数")
.register(registry);
this.orderAmountSummary = DistributionSummary.builder("order.amount")
.description("订单金额分布")
.baseUnit("yuan")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
}
@Autowired
private OrderMapper orderMapper;
public OrderDTO createOrder(CreateOrderRequest req) {
// 使用 Timer 记录耗时
return orderCreateTimer.record(() -> {
try {
Order order = buildOrder(req);
orderMapper.insert(order);
orderCreatedCounter.increment();
orderAmountSummary.record(req.getTotalAmount().doubleValue());
pendingOrderCount.incrementAndGet();
return convertToDTO(order);
} catch (Exception e) {
orderFailedCounter.increment();
log.error("创建订单失败", e);
throw e;
}
});
}
// 定期刷新仪表盘数据(Gauge 的值是动态的,这里只是示例)
@Scheduled(fixedRate = 30000)
public void refreshPendingOrderCount() {
int count = orderMapper.countByStatus(1);
pendingOrderCount.set(count);
}
}2.3 使用 @Timed 注解(更简洁的方式)
@Service
public class PaymentService {
// @Timed 注解自动记录方法执行时间
@Timed(value = "payment.process.duration",
description = "支付处理耗时",
percentiles = {0.5, 0.95, 0.99},
histogram = true)
public PaymentResult processPayment(PaymentRequest req) {
// 方法执行时间自动被 Micrometer 记录
return doProcess(req);
}
}注意:@Timed 需要配合 TimedAspect Bean 才能生效:
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}三、Prometheus 配置:指标采集与存储
3.1 Prometheus 配置文件
# prometheus.yml
global:
scrape_interval: 15s # 每 15 秒采集一次
evaluation_interval: 15s # 每 15 秒评估一次告警规则
scrape_configs:
# 静态配置(简单场景)
- job_name: 'order-service'
static_configs:
- targets: ['order-service:8080']
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
# 通过 Nacos 服务发现动态采集(推荐生产使用)
- job_name: 'spring-cloud-services'
nacos_sd_configs:
- server: 'nacos-server:8848'
namespace_id: 'prod'
relabel_configs:
- source_labels: [__meta_nacos_service_name]
target_label: service
- source_labels: [__address__]
target_label: __address__
regex: '(.*):.*'
replacement: '${1}:8080'
metrics_path: '/actuator/prometheus'
# 告警规则文件
rule_files:
- "/etc/prometheus/alerts/*.yml"
# Alertmanager 配置
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']3.2 告警规则配置
# /etc/prometheus/alerts/spring-cloud.yml
groups:
- name: spring-cloud-alerts
rules:
# 接口错误率告警
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application, uri)
/
sum(rate(http_server_requests_seconds_count[5m])) by (application, uri)
> 0.05
for: 2m # 持续 2 分钟才触发(避免瞬间抖动误报)
labels:
severity: critical
annotations:
summary: "服务 {{ $labels.application }} 接口错误率过高"
description: "接口 {{ $labels.uri }} 5分钟错误率为 {{ $value | humanizePercentage }}"
# 接口响应时间告警
- alert: SlowResponse
expr: |
histogram_quantile(0.95,
sum(rate(http_server_requests_seconds_bucket[5m])) by (application, uri, le)
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "服务 {{ $labels.application }} P95 响应时间过高"
description: "接口 {{ $labels.uri }} P95 响应时间为 {{ $value }}s"
# 连接池告警
- alert: ConnectionPoolExhausted
expr: |
hikaricp_connections_active / hikaricp_connections_max > 0.9
for: 1m
labels:
severity: critical
annotations:
summary: "{{ $labels.application }} 数据库连接池接近耗尽"
description: "连接使用率为 {{ $value | humanizePercentage }}"
# JVM 内存告警
- alert: JvmHeapUsageHigh
expr: |
jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.application }} JVM 堆内存使用率过高"
description: "堆内存使用率 {{ $value | humanizePercentage }}"四、Grafana Dashboard:让数据可见
4.1 Spring Cloud 微服务监控面板关键指标
一个标准的微服务 Dashboard 应该包含:
黄金指标(RED 方法):
- Rate(请求速率):QPS,按服务和接口分类
- Errors(错误率):5xx 比例
- Duration(响应时间):P50/P95/P99
资源指标:
- JVM 堆内存使用率和 GC 频率
- 线程数(active、waiting)
- 数据库连接池使用率
// 注册自定义 Prometheus 指标,供 Grafana 展示
@Configuration
public class CustomMetricsConfig {
@Bean
public MeterBinder customBinder() {
return registry -> {
// 注册一个多维度的业务指标
// Grafana 可以按 channel 和 payment_method 维度过滤和聚合
Counter.builder("payment.transaction.total")
.description("支付交易总数")
.tag("channel", "wechat")
.tag("payment_method", "quick_pay")
.register(registry);
};
}
}踩坑一:指标 label 基数爆炸(Cardinality Explosion)
现象:Prometheus 内存使用急剧增加,查询变慢。
原因:某个指标的 tag 值域太大(如把 user_id 作为 tag),导致时间序列数量爆炸。如果有 100 万用户,一个带 user_id tag 的指标就会产生 100 万条时间序列。
解法:tag 的值域要有界且可枚举(如 status、service_name、http_method),绝对不要用 user_id、order_id、request_id 这类无界值作为 tag。
踩坑二:Grafana 面板查询性能差
现象:Grafana Dashboard 加载缓慢,部分 Panel 超时。
原因:查询时间范围太长(比如过去 30 天),或者正则表达式 label 匹配导致全扫。
解法:
- 为高频查询的面板预先计算并缓存(Prometheus Recording Rules)
- 合理设置默认时间范围(通常最近 6-24 小时)
- 用精确匹配代替正则匹配
# Prometheus Recording Rules(预计算常用聚合)
groups:
- name: recording_rules
rules:
- record: job:http_requests_total:rate5m
expr: sum(rate(http_server_requests_seconds_count[5m])) by (application, status)五、完整链路告警流程
Prometheus 检测异常
↓ (触发告警规则)
Alertmanager
↓ (分组、静默、路由)
├── 企业微信/钉钉(P2 告警)
├── 电话/短信(P1 告警,需人工立刻处理)
└── PagerDuty(On-call 值班)监控体系建好之后,要定期做"混沌测试":故意制造异常(关掉一个服务、制造慢 SQL),验证告警能否在 1-2 分钟内触发,验证 Grafana 面板能否清晰展示异常。
不做演练的监控系统,在关键时刻往往无法发挥作用。
微服务监控是一个需要持续投入的工程。从接入 Micrometer 开始,到配置告警规则,再到建立 On-call 流程,每一步都让系统的可观测性提升一个档次。投入可观测性的团队,故障平均修复时间(MTTR)通常比没有监控的团队快 3-5 倍。
