【你好Resilience4j】五:Resilience4j熔断器Circuit Breaker(三)指标收集

本文深入解析Resilience4j中的两种滑动窗口机制——基于计数的FixedSizeSlidingWindowMetrics和基于时间的SlidingTimeWindowMetrics,阐述其在熔断器指标收集中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

每日一句

用微笑告诉别人,今天的我比昨天强,今后也一样

前言

指标收集对于熔断器的重要作用不言而喻。熔断器的各个状态的切换都依赖于指标的收集。而各种熔断降级组件收集的思路大体是一致的 (大部分的指标都可以通过滑动窗口来收集) 只不过实现不同而已。熟悉hystrix的朋友都知道它是通过Rxjava来实现的。而本篇文章来将探讨Resilience4j的收集指标机制。

Metrics

指标接口,有两种实现 基于计数的滑动窗口 FixedSizeSlidingWindowMetrics 和基于时间滑动窗口 SlidingTimeWindowMetrics

/**
 * 记录一个请求 返回一个快照.
 */
Snapshot record(long duration, TimeUnit durationUnit, Outcome outcome);

/**
 * 获取一个指标的快照
 */
Snapshot getSnapshot();

Snapshot(指标快照)

/**
  *返回总调用时长
  */
Duration getTotalDuration();

/**
 *平均调用时间
 */
Duration getAverageDuration();

/**
 * 返回低于特定阈值的调用
 */
int getTotalNumberOfSlowCalls();

/**
 * 返回当前慢请求的数量
 */
int getNumberOfSlowSuccessfulCalls();

/**
 *返回当前失败调用的数量,该数量低于某个特定阈值。
 */
int getNumberOfSlowFailedCalls();

/**
 * 返回低于特定阈值的(配置文件中配置的)请求的百分比
 */
float getSlowCallRate();

/**返回当前成功调用的数量。
 */
int getNumberOfSuccessfulCalls();

/**返回当前失败的调用数。
 */
int getNumberOfFailedCalls();

/**返回所有调用的当前总数。
 */
int getTotalNumberOfCalls();

/**返回当前失败率(百分比)。
 */
float getFailureRate();

指标快照接口 有且仅有一个实现SnapshotImpl。该实现类根据以下的5个值来计算上述接口所需要的值。

  • 调用总耗时 totalDurationInMillis
  • 慢调用数量 totalNumberOfSlowCalls
  • 慢调用失败的数量 totalNumberOfSlowFailedCalls
  • 失败调用数量 totalNumberOfFailedCalls
  • 总调用数量 totalNumberOfCalls

我们来解释一下上面几个变量的。
totalNumberOfSlowFailedCalls 这个很容器理解 就是代表在某一段时间内的总的调用数量。
totalNumberOfSlowCalls 慢调用数量。还记得上一篇我们说配置的时候有一个配置 slowCallDurationThreshold (慢执行的阈值,大于该值的调用都会被记入慢调用)。

totalNumberOfSlowFailedCalls 慢执行的失败的数量,也就是说这些执行时间都大于我们配置的slowCallDurationThreshold 阈值。并且执行还失败了。
totalNumberOfFailedCalls 失败的执行数量

那么有了以上的这些值 就能得到Snapshot这个接口定义的方法的返回值。所以SnapshotImpl实现很简单 我们就不贴源码占用位置了。

FixedSizeSlidingWindowMetrics

基于计数的滑动窗口。该实现是基于固定大小的数组 Measurement[]存放每次调用的结果 totalAggregation是结果的聚合。 但是totalAggregation最多只能聚合windowSize 个调用结果。作为Resilience4j 断路器指标的默认实现。
在聊FixedSizeSlidingWindowMetrics之前 我们先来对涉及到的基础类进行清扫。

AbstractAggregation

从名字看出事一个聚合操作的类。

//下面是上面 我们聊的五大基础变量 
long totalDurationInMillis = 0;
//慢执行的数量
int numberOfSlowCalls = 0;
//慢执行失败的数量
int numberOfSlowFailedCalls = 0;
//执行失败的数量
int numberOfFailedCalls = 0;
//总数量
int numberOfCalls = 0;

//下面的方法就是对每次请求的记录。值得注意的是 这个操作是 非线程安全的哦
void record(long duration, TimeUnit durationUnit, Metrics.Outcome outcome) {
    this.numberOfCalls++;
    this.totalDurationInMillis += durationUnit.toMillis(duration);
    //主要是下面的这个switch  我们看到执行失败被划分为 慢执行失败和 普通的失败
    switch (outcome) {
        case SLOW_SUCCESS:
            numberOfSlowCalls++;
            break;
        case SLOW_ERROR:
            numberOfSlowCalls++;
            numberOfFailedCalls++;
            numberOfSlowFailedCalls++;
            break;
        case ERROR:
            numberOfFailedCalls++;
            break;
    }
}

TotalAggregation

该聚合只是增加了一个移除桶的方法。 很容易理解 从总聚合总移除一个桶

 void removeBucket(AbstractAggregation bucket) {
        this.totalDurationInMillis -= bucket.totalDurationInMillis;
        this.numberOfSlowCalls -= bucket.numberOfSlowCalls;
        this.numberOfSlowFailedCalls -= bucket.numberOfSlowFailedCalls;
        this.numberOfFailedCalls -= bucket.numberOfFailedCalls;
        this.numberOfCalls -= bucket.numberOfCalls;
    }

Measurement

该类在聚合的基础上提供了一个重置的方法。

   void reset() {
        this.totalDurationInMillis = 0;
        this.numberOfSlowCalls = 0;
        this.numberOfFailedCalls = 0;
        this.numberOfCalls = 0;
    }

值得注意的是 这个重置方法 并没有对numberOfSlowFailedCalls进行重置。

PartialAggregation

该类是public 修饰的,是唯一的一个开放的一个聚合类。
PartialAggregation: 部分聚集

private long epochSecond;
//构造方法需要一个时间 意义就是这段时间的聚合
  PartialAggregation(long epochSecond) {
      this.epochSecond = epochSecond;
  }
  void reset(long epochSecond) {
      this.epochSecond = epochSecond;
      this.totalDurationInMillis = 0;
      this.numberOfSlowCalls = 0;
      this.numberOfFailedCalls = 0;
      this.numberOfCalls = 0;
  }

FixedSizeSlidingWindowMetrics

有了上面的聚合类的支持 我们再来看FixedSizeSlidingWindowMetrics

private final int windowSize;
private final TotalAggregation totalAggregation;
private final Measurement[] measurements;
int headIndex;
属性说明
  • windowSize: 窗口的大小 可以通过配置项 slidingWindowSize来设定。
  • totalAggregation: 总聚合对每次请求的结果进行聚合
  • measurements: 统计固定窗口的指标。你可以理解为它是对一个窗口的所有指标的统计。例如一个窗口大小为30 那么它就是对这30次的执行结果进行统计。等到第31次的时候就会移除第一次 加上第31的结果。会一直统计30次的结果,滑动统计。
  • headIndex: 一个标志位,
构造方法
  public FixedSizeSlidingWindowMetrics(int windowSize) {
    this. = windowSize;
    this.measurements = new Measurement[this.windowSize];
    this.headIndex = 0;
    for (int i = 0; i < this.windowSize; i++) {
        measurements[i] = new Measurement();
    }
    this.totalAggregation = new TotalAggregation();
    }

该类只提供了一个构造方法,用于对measurementswindowSizetotalAggregation等属性做初始化。

滑动窗口实现
//这个方法是滑动窗口算法的实现 的核心逻辑
 private Measurement moveWindowByOne() {
   //将下标向下移动一位 如果下标已经是数组的最大值 向下一位就会移动到数组的第一位
   moveHeadIndexByOne();
   //获取最新的桶
   Measurement latestMeasurement = getLatestMeasurement();
   //如果最新的桶有值就从总聚合中删除
   totalAggregation.removeBucket(latestMeasurement);
   //重置最新的桶
   latestMeasurement.reset();
   return latestMeasurement;
}
// 向下移动一个桶 这个是循环数组的下标 
void moveHeadIndexByOne() {
  this.headIndex = (headIndex + 1) % windowSize;
}
//获取最新的桶
private Measurement getLatestMeasurement() {
   return measurements[headIndex];
}        

通过上面的介绍 你可能对 moveWindowByOne 这个方法的操作有疑问。

假如我们圆形数组(measurements)有10个元素,也就是滑动窗口大小为10。那么当headIndex值为9的时候,这个时候来了一个请求 需要记录一个执行结果 通过 moveHeadIndexByOne 的计算 headIndex为0
getLatestMeasurement 获取最新的桶 就是数组下标为 0的元素。但是此时 下标为0的元素是有值的。需要我们从总聚合中移除该值 并且重置此处的值 才能保证 滑动窗口的值是最近10次执行的结果的聚合。

getSnapshot& record
//记录一次请求结果的执行值
@Override
public synchronized Snapshot record(long duration, TimeUnit durationUnit, Outcome outcome) { 
    //总聚合记录
    totalAggregation.record(duration, durationUnit, outcome);
    //滑动记录
    moveWindowByOne().record(duration, durationUnit, outcome);
    return new SnapshotImpl(totalAggregation);
}
//获取一个总聚合的快照
@Override
public synchronized Snapshot getSnapshot() {
    //直接获取总聚合的快照信息
    return new SnapshotImpl(totalAggregation);
}

上面两个方法是实现Metrics接口的方法!

SlidingTimeWindowMetrics

了解完基于计数的滑动窗口 再来看基于时间的 那就很easy了。维度不同而已。

    public SlidingTimeWindowMetrics(int timeWindowSizeInSeconds, Clock clock) {
        this.clock = clock;
        /**初始化窗口大小 1s一个存储桶 如果timeWindowSizeInSeconds为10 就会分成10个存储桶 并且总的窗口大小为10s*/
        this.timeWindowSizeInSeconds = timeWindowSizeInSeconds;
        /**下面初始化存储桶 初始化的时候会初始化存储桶的时间*/
        this.partialAggregations = new PartialAggregation[timeWindowSizeInSeconds];
        this.headIndex = 0;
        //初始化 partialAggregations 注意这里是epochSecond++  所以是一秒一个存储桶 这个是写死的。如果窗口大小是 10s 那么久会初始化10个存储桶
        long epochSecond = clock.instant().getEpochSecond();
        for (int i = 0; i < timeWindowSizeInSeconds; i++) {
            partialAggregations[i] = new PartialAggregation(epochSecond);
            epochSecond++;
        }
        this.totalAggregation = new TotalAggregation();
    }

上面有一个地方需要注意,在初始化滑动窗口的时候 每秒一个存储桶 这个是写死的。了解Hystrix的朋友应该知道 Hystrix这个桶时长是可配置的

record方法

 @Override
    public synchronized Snapshot record(long duration, TimeUnit durationUnit, Outcome outcome) {
        //总聚合增加一个记录
        totalAggregation.record(duration, durationUnit, outcome);
        //滑动记录 
        moveWindowToCurrentEpochSecond(getLatestPartialAggregation())
            .record(duration, durationUnit, outcome);
        return new SnapshotImpl(totalAggregation);
    }

重点就是moveWindowToCurrentEpochSecond这个方法。继续跟进这个方法

 private PartialAggregation moveWindowToCurrentEpochSecond(
        PartialAggregation latestPartialAggregation) {
        //这里使用clock实例获取当前对象
        long currentEpochSecond = clock.instant().getEpochSecond();
        /**当前时间 减去最新的一个桶的时间 计算出要滑动的窗口*/
        long differenceInSeconds = currentEpochSecond - latestPartialAggregation.getEpochSecond();
        /**如果还在当前的统计窗口内 直接返回最新的桶*/
        if (differenceInSeconds == 0) {
            return latestPartialAggregation;
        }
        /**如果要移动的窗口个数  这里做了一个判断如果需要移动的个数大于窗口的总大小 就将移动的窗口个数改变成窗口的总大小*/
        long secondsToMoveTheWindow = Math.min(differenceInSeconds, timeWindowSizeInSeconds);
        PartialAggregation currentPartialAggregation;
        /**下面就是滑动窗口的逻辑了 滑动的过程*/
        do {
            secondsToMoveTheWindow--;
            //移动headIndex到下一个窗口(存储桶)
            moveHeadIndexByOne();
            //获取下一个存储桶
            currentPartialAggregation = getLatestPartialAggregation();
            //从总的统计中移除当前存储桶
            totalAggregation.removeBucket(currentPartialAggregation);
            //重置当前存储桶 并对epochSecond赋值
            // 例如我们当前向前移动了3个窗口 那么移动的第一个窗口对应的时间就是当前时间减去2s 以此类推
            currentPartialAggregation.reset(currentEpochSecond - secondsToMoveTheWindow);
        } while (secondsToMoveTheWindow > 0);
        return currentPartialAggregation;
    }

主要的逻辑就在上面这个方法中。

demo

Metrics metrics = new FixedSizeSlidingWindowMetrics(5);
//记录一次成功的执行 
Snapshot snapshot = metrics.record(100, TimeUnit.MILLISECONDS, Metrics.Outcome.SUCCESS);
assertThat(snapshot.getTotalNumberOfCalls()).isEqualTo(1);
assertThat(snapshot.getNumberOfSuccessfulCalls()).isEqualTo(1);
assertThat(snapshot.getNumberOfFailedCalls()).isEqualTo(0);
assertThat(snapshot.getTotalNumberOfSlowCalls()).isEqualTo(0);
assertThat(snapshot.getNumberOfSlowSuccessfulCalls()).isEqualTo(0);
assertThat(snapshot.getNumberOfSlowFailedCalls()).isEqualTo(0);
assertThat(snapshot.getTotalDuration().toMillis()).isEqualTo(100);
assertThat(snapshot.getAverageDuration().toMillis()).isEqualTo(100);
assertThat(snapshot.getFailureRate()).isEqualTo(0);

总结

Resilience4j的指标统计功能到这就完了。它相对于Hystrix的还是比较好理解的。把基于计数的和基于时间的滑动窗口两个核心的类搞明白就好了。其中默认是基于计数的滑动窗口。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值