Metrics

SpringBoot 在较低的版本中使用 [metrics] 作为 metrics 统计工具,在新版本中使用 micrometer 替换了 dropwizard-metrics 包实现 metric 统计。

从横向架构来讲,一个 metrics 工具核心功能通常包含:

  1. 采集
  2. 计算
  3. 展示、上报

1. 指标采集与计算

1.1 指标(Meter)

dropwizard-metrics 定义的指标包含:

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。核心思路是:

  1. 采样集:定长数组
  2. 采样集数据更新原则:当采样集未满时,直接向采样集中增加。采样集满时,随机替换数组的第 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 个数据。核心思路是:

  1. 采样集:长度为 N 的数组。
  2. 采样集数据更新原则:数组未满,向后累加;数组满,从第一个开始覆盖。

核心代码:

    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 秒的数据。

核心思路:

  1. 采样集:key 是时间,value 是事件值的 Map。
  2. 采样集数据更新原则:只保存最近 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.」其特点如下:

  1. High Dynamic Range。统计值范围大。
  2. The HDR Histogram maintains a fixed cost in both space and time。占用资源低。

MicroMeter 统计百分比的核心代码为 TimeWindowPercentileHistogram,其配置与说明在 DistributionStatisticConfig 中。关键的概念和配置为:

2. 指标展示与上报

这两个 Metrics 包都集成了上报逻辑。

3. 其他参考