基于时间序列检测算法的智能报警实现

前言

服务的高可用离不开稳定的监控,如果服务出现了问题,作为开发者能第一时间发现问题,修复上线是业务止损的最好方法,随着业务飞速发展,对系统的稳定性有了更高的要求,传统的基础告警指标和触发器设计方案,在使用上存在很多限制,报警规则配置依赖开发人员主观经验,配置一条高可用规则需要多次实践,等到系统或外部依赖发生变化,又要依据新的数据调整规则,不仅开发筋疲力竭,报警的误报漏报也十分严重,面对这些问题,我们设计研发了一套智能报警系统,它与传统报警最大的不同在于它集成了机器学习算法,具有自我学习的功能,根据系统的演进自动调节阈值,能够很大程度降低误报和漏报。

数据分析

将某项目上报数据中的错误概率绘制成折线图,可以发现一些错误的规律(这里给出的是概率统计值):

基于时间序列检测算法的智能报警实现

从时间上看,错误概率时间序列有两个明显的特征(如上图所示):

  • 周期性。每天错误量的变化趋势都大致相同,午高峰和晚高峰样本数大,错误概率稳定在一定值,凌晨到早8点左右概率值不稳定。
  • 独立性。错误出现的概率只受系统内部的影响,不会随着流量的波动而改变,即样本足够时,概率变化可以和系统内部出现的问题一一对应。

在最开始,我们尝试用传统方案配置告警规则(如下图):

基于时间序列检测算法的智能报警实现

但是如果简单的根据错误个数来配置报警,业务会陷入一个矛盾:假设按照高峰期流量设置为100个异常,则低峰期不会报警,反之设置低峰期10个异常,那么高峰期会持续报警

为了解决这个问题,我们将当前时刻和前一时刻序列值比较,采用监控增速的算法,但是这种方式也没有排除周期性对数据的影响,误报率和漏报率都比较大。后期发现平台有服务提供了基线数据模型服务,基线数据模型考虑到了时间序列的周期性特征,于是我们尝试将业务数据上传到美团点评的服务治理平台,试验后发现基线模型忽略了实时性特征,导致了数据验证不及时,依旧存在大量的误报漏报,RD对于报警已经麻木,出现问题时不能及时响应,因此,急需一种新的异常检测模型,提高报警的准确率。

由于数据是时间序列模型,且具有很强的周期性,我们选择了移动平均的替代算法,三次指数平滑法三次指数平滑算法可以对同时含有趋势季节性的时间序列进行预测,该算法是基于一次指数平滑和二次指数平滑算法的。

预测器

一次指数平滑算法基于以下的递推关系:

si=αxi+(1-α)si-1

其中α是平滑参数,si是之前i个数据的平滑值,取值为[0,1],α越接近1,平滑后的值越接近当前时间的数据值,数据越不平滑,α越接近0,平滑后的值越接近前i个数据的平滑值,数据越平滑,α的值通常可以多尝试几次以达到最佳效果。

三次指数平滑累加累乘两种方法,下面是累加的三次指数平滑

si=α(xi-pi-k)+(1-α)(si-1+ti-1)

ti=ß(si-si-1)+(1-ß)ti-1

pi=γ(xi-si)+(1-γ)pi-k 其中k为周期,累加三次指数平滑的预测公式为: xi+h=si+hti+pi-k+(h mod k)

下式为累乘的三次指数平滑

si=αxi/pi-k+(1-α)(si-1+ti-1)

ti=ß(si-si-1)+(1-ß)ti-1

pi=γxi/si+(1-γ)pi-k 其中k为周期,累乘三次指数平滑的预测公式为: xi+h=(si+hti)pi-k+(h mod k),α,ß,γ的值都位于[0,1]之间,可以多试验几次以达到最佳效果。

下面给出算法的部分实现(核心部分):

function calcHoltWinters
  (data, st1, bt1, alpha, beta, gamma, seasonal, period, m) {
  var len = data.length
  var st = Array(len)
  var bt = Array(len)
  var it = Array(len)
  var ft = Array(len)
  var i

  st[1] = st1
  bt[1] = bt1

  for (i = 0; i < len; i++) {
    ft[i] = 0.0
  }

  for (i = 0; i < period; i++) {
    it[i] = seasonal[i]
  }

  for (i = 2; i < len; i++) {
    if (i - period >= 0) {
      st[i] = ((alpha * data[i]) / it[i - period]) +
        ((1.0 - alpha) * (st[i - 1] + bt[i - 1]))
    } else {
      st[i] = (alpha * data[i]) + ((1.0 - alpha) *
                                   (st[i - 1] + bt[i - 1]))
    }

    bt[i] = (gamma * (st[i] - st[i - 1])) +
      ((1 - gamma) * bt[i - 1])

    if (i - period >= 0) {
      it[i] = ((beta * data[i]) / st[i]) +
        ((1.0 - beta) * it[i - period])
    }

    if (i + m >= period) {
      ft[i + m] = (st[i] + (m * bt[i])) *
        it[i - period + m]
    }
  }

  return ft
}

function getForecast (data, alpha, beta, gamma, period, m) {
  var seasons, seasonal, st1, bt1

  if (!validArgs(data, alpha, beta, gamma, period, m)) {
    return
  }

  seasons = Math.floor(data.length / period)
  st1 = data[0]
  bt1 = initialTrend(data, period)
  seasonal = seasonalIndices(data, period, seasons)

  return calcHoltWinters(
    data,
    st1,
    bt1,
    alpha,
    beta,
    gamma,
    seasonal,
    period,
    m
  )
}

function seasonalIndices (data, period, seasons) {
  var savg, obsavg, si, i, j

  savg = Array(seasons)
  obsavg = Array(data.length)

  si = Array(period)

  for (i = 0; i < seasons; i++) {
    savg[i] = 0.0
  }
  for (i = 0; i < period; i++) {
    si[i] = 0.0
  }

  for (i = 0; i < seasons; i++) {
    for (j = 0; j < period; j++) {
      savg[i] += data[(i * period) + j]
    }
    savg[i] /= period
  }
  for (i = 0; i < seasons; i++) {
    for (j = 0; j < period; j++) {
      obsavg[(i * period) + j] = data[(i * period) + j] / savg[i]
    }
  }
  for (i = 0; i < period; i++) {
    for (j = 0; j < seasons; j++) {
      si[i] += obsavg[(j * period) + i]
    }
    si[i] /= seasons
  }

  return si
}

我们同时实现了一个暴力枚举算法,反复拟合出最符合业务数据的参数 :[0.2 、0.1、 0.45]

基于时间序列检测算法的智能报警实现

预测器部分已经基本完成,接下来就是触发器相关的设计:

触发器

触发器和检测器的关系如下图所示:

基于时间序列检测算法的智能报警实现

当预测器通过前几天的数据分析两处理预测出当天的理想值后,触发器每隔一个时间间隔获取当天凌晨0点至触发器当前时间点的数据,理想值与真实值经过比较器处理,判断真实值是否符合预期而对应是否触发报警。

触发器的设计如下图所示:

基于时间序列检测算法的智能报警实现

大体上触发器做的事是——真实值与预测值对比,不满足预期则报警。为提高报警的准确度,通过对预测数据分时间段计算方差,方差越大则数据曲线波动越大。当波动程度大时,对应的时间段所设置的阈值应设置更宽避免较多的误报。则当相同时间段内预测曲线 、真实曲线的均值差大于预测曲线的某个倍数时则触发器触发报警,这就是通过离散度和预测值得到相对动态的阈值,我们目前处于当前阶段。

但是检测不同数据类型时这个倍数不同,针对不同类型的报错需要设定不同的倍数值。人工统一设定的倍数值还是不够准确,易造成漏报(倍数太大)或者误报(倍数太小)。所以对于我们来说更智能的动态阈值是能从历史数据学习到这个动态的倍数值,这是下个阶段的目标,让波动阈值区域尽量收的更紧凑。如下图曲线外包裹区域:

基于时间序列检测算法的智能报警实现

总结

在我们监控系统上报数据后,基于已上报的数据我们可以做智能报警,而不是再像普通的报警系统,通过大量人工针对性的分段阈值设定,过于依赖人工经验性判断。基于机器学习的智能报警会更准确和高效。当然有了数据不只是可以做智能报警,这套系统还有更多可深入挖掘和发掘的功能,智能报警只是人工智能和监控领域结合的初步成果。

相关推荐