服务的高可用离不开稳定的监控,若是服务出现了问题,做为开发者能第一时间发现问题,修复上线是业务止损的最好方法,随着业务飞速发展,对系统的稳定性有了更高的要求,传统的基础告警指标和触发器设计方案,在使用上存在不少限制,报警规则配置依赖开发人员主观经验,配置一条高可用规则须要屡次实践,等到系统或外部依赖发生变化,又要依据新的数据调整规则,不只开发筋疲力竭,报警的误报漏报也十分严重,面对这些问题,咱们设计研发了一套智能报警系统,它与传统报警最大的不一样在于它集成了机器学习算法,具备自我学习的功能,根据系统的演进自动调节阈值,可以很大程度下降误报和漏报。算法
数据分析bash
将某项目上报数据中的错误几率绘制成折线图,能够发现一些错误的规律(这里给出的是几率统计值):机器学习
从时间上看,错误几率时间序列有两个明显的特征(如上图所示):学习
在最开始,咱们尝试用传统方案配置告警规则(以下图):ui
可是若是简单的根据错误个数来配置报警,业务会陷入一个矛盾:假设按照高峰期流量设置为100个异常,则低峰期不会报警,反之设置低峰期10个异常,那么高峰期会持续报警人工智能
为了解决这个问题,咱们将当前时刻和前一时刻序列值比较,采用监控增速的算法,可是这种方式也没有排除周期性对数据的影响,误报率和漏报率都比较大。后期发现平台有服务提供了基线数据模型服务,基线数据模型考虑到了时间序列的周期性特征,因而咱们尝试将业务数据上传到美团点评的服务治理平台,试验后发现基线模型忽略了实时性特征,致使了数据验证不及时,依旧存在大量的误报漏报,RD对于报警已经麻木,出现问题时不能及时响应,所以,急需一种新的异常检测模型,提升报警的准确率。
spa
因为数据是时间序列模型,且具备很强的周期性,咱们选择了移动平均的替代算法,三次指数平滑法。 三次指数平滑算法能够对同时含有趋势和季节性的时间序列进行预测,该算法是基于一次指数平滑和二次指数平滑算法的。设计
一次指数平滑算法基于如下的递推关系:3d
si=αxi+(1-α)si-1 code
其中α是平滑参数,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.一、 0.45]
预测器部分已经基本完成,接下来就是触发器相关的设计:
触发器和检测器的关系以下图所示:
当预测器经过前几天的数据分析两处理预测出当天的理想值后,触发器每隔一个时间间隔获取当天凌晨0点至触发器当前时间点的数据,理想值与真实值通过比较器处理,判断真实值是否符合预期而对应是否触发报警。
触发器的设计以下图所示:
大致上触发器作的事是——真实值与预测值对比,不知足预期则报警。为提升报警的准确度,经过对预测数据分时间段计算方差,方差越大则数据曲线波动越大。当波动程度大时,对应的时间段所设置的阈值应设置更宽避免较多的误报。则当相同时间段内预测曲线 、真实曲线的均值差大于预测曲线的某个倍数时则触发器触发报警,这就是经过离散度和预测值获得相对动态的阈值,咱们目前处于当前阶段。
可是检测不一样数据类型时这个倍数不一样,针对不一样类型的报错须要设定不一样的倍数值。人工统一设定的倍数值仍是不够准确,易形成漏报(倍数太大)或者误报(倍数过小)。因此对于咱们来讲更智能的动态阈值是能从历史数据学习到这个动态的倍数值,这是下个阶段的目标,让波动阈值区域尽可能收的更紧凑。以下图曲线外包裹区域:
在咱们监控系统上报数据后,基于已上报的数据咱们能够作智能报警,而不是再像普通的报警系统,经过大量人工针对性的分段阈值设定,过于依赖人工经验性判断。基于机器学习的智能报警会更准确和高效。固然有了数据不仅是能够作智能报警,这套系统还有更多可深刻挖掘和发掘的功能,智能报警只是人工智能和监控领域结合的初步成果。