java实现一个不带次数变量的加权平均值算法

背景

我们希望通过一个mybatis的拦截器interceptor来对每个sql做慢sql检查,并且做相应的打点告警。对于慢sql的判断方式目前有两种

第一种是通过一个固定阈值来判断,比如凡是超过1秒的都是慢sql,这种实现方式就简单了,但是这种方式可能需要随着时间推移调整阈值防止无效告警

第二种是每次计算该sql的平均耗时,如果某次超过了平均耗时的N倍,就是慢sql。

这种方式可以检测用于突发的锁表,数据库连接异常,数据库宕机等异常情况。当然第一种方式也可以检测到,但如果固定阈值设置的过高,就很难发现一些短时间内的慢sql。

还能检测的是不当的查询条件引起的慢sql,比如有一个联合索引,class_name,create_time。其中create_time是区间查询。正常情况下create_time的时间区间是在一小时内,但是如果某次查询时间范围太大,导致扫描的记录太多导致的慢sql,就能通过这种方式检测到。

以下是结合两种思路的代码实现(这里的结果只是预估平均值,不适用于需要绝对平均值的场景

public class AlertOnTimesOfAverageCostTimeUtil {

  @Value("${times.of.average.should.be.alert:10}")
  private int alertOfTimesOfAverageCostTime;
  @Value("${regular.timeout.seconds.on.slow.sql.execute:1L}")
  private Long slowSqlTime;
  private static final Long SEC_TO_MILLS = 1000L;
  private static final double DEFAULT_COST_TIME_WHEN_NULL = 0.0d;
  private static final double WEIGHT_ON_CAL_NEW_AVG_TIME = 0.003d;

  private static final Map sqlCostTimeMap = new ConcurrentHashMap<>();

  public boolean shouldDoTheMetricWork(String sql, Long costTime) {
    if (alertOfTimesOfAverageCostTime <= 0 || slowSqlTime <= 0) {
      //主要防止内存泄漏,因为后面finally会缓存每个sql模板的平均耗时
      return false;
    }
    try {
      if (slowSqlTime > 0 && costTime.doubleValue() >= slowSqlTime * SEC_TO_MILLS) {
        return true;
      } else if (alertOfTimesOfAverageCostTime > 0
          && sqlCostTimeMap.containsKey(sql) && sqlCostTimeMap.get(sql) * alertOfTimesOfAverageCostTime <= costTime) {
        return true;
      } else {
        return false;
      }
    } finally {
      double averageTime = sqlCostTimeMap.getOrDefault(sql, DEFAULT_COST_TIME_WHEN_NULL);
      //权重设置为0.003,此加权平均值和实际平均值的误差在2%~4%
      sqlCostTimeMap.put(sql, averageTime + (costTime.doubleValue() / SEC_TO_MILLS - averageTime) * WEIGHT_ON_CAL_NEW_AVG_TIME / (averageTime + 1));
    }
  }
}

最后一行计算平均值时,并没有记录历史的请求数量,而是直接通过每次的耗时做加权平均。当权重取到0.003时,其与实际平均值的误差是在2%~4%之间,还是符合我的要求的。

我就是不想每次都把次数加进来去计算平均耗时) 

以下是测试代码

public static void main(String[] args) {
    for (int k = 1000; k > 0; k--) {
      Double max = Double.MIN_VALUE;
      Double min = Double.MAX_VALUE;
      Double ave = Double.MAX_VALUE;
      for (int j = 0; j < 1000; j++) {
        AlertOnTimesOfAverageCostTimeUtil util = new AlertOnTimesOfAverageCostTimeUtil();
        double sum = 0;
        for (int i = 0; i < 10000; i++) {
          int rando = new Random().nextInt(10000);
          sum += rando;
          util.shouldDoTheMetricWork("aa", (long) rando);
        }
        max = Math.max(max, Math.abs(util.sqlCostTimeMap.get("aa") - sum / 10000 / 1000));
        min = Math.min(min, Math.abs(util.sqlCostTimeMap.get("aa") - sum / 10000 / 1000));
        ave = sum / 10000 / 1000;
      }

      System.out.println("最大误差:"+max + ";真实平均值:" + ave);
    }


  }

最大误差:0.15007063545375843;真实平均值:4.984865
最大误差:0.1174636412384027;真实平均值:4.9742846
最大误差:0.11923799177187266;真实平均值:5.0006945
最大误差:0.123397194953621;真实平均值:4.9524842
最大误差:0.1289876768492615;真实平均值:4.9987173
最大误差:0.11650603805686899;真实平均值:5.021868400000001
最大误差:0.15616266782667765;真实平均值:4.9543595
最大误差:0.13302112368706442;真实平均值:5.0302075
最大误差:0.10326801249256956;真实平均值:5.0047261
最大误差:0.13365076974221513;真实平均值:5.0559015

 

 

 

你可能感兴趣的:(算法,java)