Metrics
SpringBoot 在较低的版本中使用 [metrics] 作为 metrics 统计工具,在新版本中使用 micrometer 替换了 dropwizard-metrics 包实现 metric 统计。
从横向架构来讲,一个 metrics 工具核心功能通常包含:
- 采集
- 计算
- 展示、上报
1. 指标采集与计算
1.1 指标(Meter)
dropwizard-metrics 定义的指标包含:
- Gauges。一个常量值,如 Queue size。其扩展实现还可能包含如 RatioGauges(包含两个 Gauges 指标,返回的 ratio 值是通过两个指标做除法获得)、CachedGuages(缓存具体的 Guages 指标一段时间,每次 getValue 可能获取的是缓存值)。
- Counters。计数。用于统计类似调用次数等指标。
- Histograms。统计数据流的分布,如最小值、最大值、平均值以及分位数(如95t percentile)。具体如调用时间的统计,平均线、99线、999线。
- Meter。A meter measures the rate at which a set of events occur。如 CPU load 值的统计。
- Timer。一段时间内的 Histograms 和 Meter 指标。
metrics-core 中包含的 Meter 同样是 Counters、Guages、Timers。
1.2 指标采集
JVM 相关参数一般是通过 MXBean 采集,具体可见 ManagementFactory 类。其他组件一般通过组件扩展点做数据采集。
常用组件指标采集 Metrics 工具都会默认实现,如 dropwizard-metrics
的 metrics-xxx 包以及 micrometer
的 binder pacakge。
1.3 指标计算与算法
计数类指标如果涉及到多线程修改通常实现是使用 concurrent 包下的 automic 类。
这里详细说一下用于分布统计的 Histograms 统计算法。
Histograms 统计。如果是计算平均数或者统计总数,只需要维持一个中间变量即可,但是对于如百分数 quantiles 的统计需要记录所有的事件值才可计算出最终的结果,如耗时统计的 99 线值。对于小型数据集,这种方案是可行的,但是现实中的统计都是基于持续增加的流式数据,在流式数据的基础上计算百分数的思路是「采样」,这个较小的、可以快速计算出统计结果的采样集合被称为「采样集」。而Histograms算法的关键就是采样集中数据的采样方案。
I dropwizard-metrics 提供的算法实现
a) Uniform Reservoirs
这种算法的理论基础是:Random Sampling with a Reservoi。核心思路是:
- 采样集:定长数组
- 采样集数据更新原则:当采样集未满时,直接向采样集中增加。采样集满时,随机替换数组的第 i 个元素。
核心代码如下:
private static final int DEFAULT_SIZE = 1028;
private final AtomicLong count = new AtomicLong();
private final AtomicLongArray values;
@Override
public void update(long value) {
final long c = count.incrementAndGet();
if (c <= values.length()) {
values.set((int) c - 1, value);
} else {
final long r = ThreadLocalRandom.current().nextLong(c);
if (r < values.length()) {
values.set((int) r, value);
}
}
}
b) Sliding Window Reservoirs
滑动窗口采样集算法。
这种算法的采样集是过去时间的 N 个数据。核心思路是:
- 采样集:长度为 N 的数组。
- 采样集数据更新原则:数组未满,向后累加;数组满,从第一个开始覆盖。
核心代码:
private final long[] measurements;
private long count;
@Override
public synchronized void update(long value) {
measurements[(int) (count++ % measurements.length)] = value;
}
c) Sliding Time Window Reservoirs
采样集的数据是最近 N 秒的数据。
核心思路:
- 采样集:key 是时间,value 是事件值的 Map。
- 采样集数据更新原则:只保存最近 N 秒的值。
核心代码:
private final ConcurrentSkipListMap<Long, Long> measurements;
@Override
public void update(long value) {
if (count.incrementAndGet() % TRIM_THRESHOLD == 0) {
trim();
}
measurements.put(getTick(), value);
}
注意:基于最近 N 秒采样的最大问题是采样集是无限大、不可控的。dropwizard 提供了 SlidingTimeWindowArrayReservoir 用于缓解这个问题产生的影响。
d) Exponentially Decaying Reservoirs
指数衰减采样集算法。理论基础:Forward Decay: A Practical Time Decay Model for Streaming Systems。同样记录最近时间段的事件值,每个事件值都映射一个权重值,时间越近,权重值越大。
核心代码:
private final ConcurrentSkipListMap<Double, WeightedSample> values;
public void update(long value, long timestamp) {
rescaleIfNeeded();
lockForRegularUsage()
try {
final double itemWeight = weight(timestamp - startTime);
final WeightedSample sample = new WeightedSample(value, itemWeight);
final double priority = itemWeight / ThreadLocalRandom.current().nextDouble();
final long newCount = count.incrementAndGet();
if (newCount <= size) {
values.put(priority, sample);
} else {
Double first = values.firstKey();
if (first < priority && values.putIfAbsent(priority, sample) == null) {
// ensure we always remove an item
while (values.remove(first) == null) {
first = values.firstKey();
}
}
}
} finally {
unlockForRegularUsage();
}
}
从采样集计算百分比值的方案一般是排序排序,然后查找。具体代码可参见 Snapshot
的实现类。
II) micrometer 提供的算法实现
micrometer 基于 HdrHistogram 实现。
HdrHistogram 全称为 「HdrHistogram: A High Dynamic Range Histogram.」其特点如下:
- High Dynamic Range。统计值范围大。
- The HDR Histogram maintains a fixed cost in both space and time。占用资源低。
MicroMeter 统计百分比的核心代码为 TimeWindowPercentileHistogram
,其配置与说明在 DistributionStatisticConfig
中。关键的概念和配置为:
- percentiles。需要计算的百分位数,如 需要计算 99 百分位,那么配置 0.99,可配置多个。
- percentilePrecision。计算百分位数值的精度,如 99 线值为 109.1,那么精度为1。
- minimumExpectedValue。采样值可能的最小值。
- maximumExpectedValue。采样值可能的最大值。
- expiry & bufferLength。RingBuffer 的长度和rotate时间。
- sla。统计采样值满足 sla 范围内的个数。
2. 指标展示与上报
这两个 Metrics 包都集成了上报逻辑。
- micrometer 可见 http://micrometer.io/docs 中的 setup 部分。
- dropwizard-metrics 可见 https://metrics.dropwizard.io/4.0.0/manual/core.html#reporters 部分。
3. 其他参考
- https://caorong.github.io/2016/07/31/hdrhistogram/