Micrometer监控的原理与误区

Metrics 注册原理

1. 基于首次注册对象引用获取指标

先来看看这行代码的实际作用:

1
Metrics.gauge(metricName("xxx"), tags, runtimeInfo, ThreadPoolRuntimeInfo::getXxx);

这行代码背后的执行逻辑是这样的:

  1. 如果这个 metric(包括名字 + tag)是第一次注册Micrometer 会绑定 runtimeInfo 对象的引用
  2. 如果 metric 已经注册过了,这次调用不会更新引用 ,只是返回已存在的 GaugeMicrometer 内部维护了一个缓存结构)
  3. 因此,即使后续你传入了新的 runtimeInfo 对象,但这个新对象并不会被绑定,Micrometer 仍然会读取最初绑定的老对象的引用值

第一次传入的runtimeInfo1被绑定后,之后无论你传入runtimeInfo2、3、4Micrometer都会忽略,只会继续读取runtimeInfo1的字段值

2. 注册的 Metric 指标什么时候更新?

既然已经注册的 Metric 指标需要更新最新的值,那是不是内部有个定时任务在收集数据呢?

其实不是的。Metric 内部并没有定时收集机制,而是当外部调用方请求指标列表时,才会从已绑定的对象中获取最新数据。这个调用方通常是 Prometheus,因为 Prometheus 会定时拉取数据

当前 Metrics 实现存在的问题

任务完成数量和拒绝策略指标展示不够直观

我们在注册完成任务数量和拒绝策略数量时,指标每次都是累计递增的

要么指标是平的,要么一直往上增长,这样不利于观察某个时间区间内的任务完成情况

Metrics 优化重构

注册增量指标:使用 DeltaWrapper 包装类维护历史值,实现 “当前值 - 上次值“ 的统计;将 deltaMap 存入缓存,便于后续更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DeltaWrapper {
// 上一周期的值
private Long lastValue;
// 当前周期的值
private Long currentValue;

/**
* 更新最新值,并记录上一次的值,便于计算 delta
*/
public synchronized void update(Long newValue) {
this.lastValue = (this.currentValue == null) ? newValue : this.currentValue;
this.currentValue = newValue;
}

/**
* 获取当前周期与上一周期之间的增量值
*/
public synchronized long getDelta() {
if (currentValue == null || lastValue == null) {
return 0;
}
return currentValue - lastValue;
}
}