服務的高可用離不開穩定的監控,若是服務出現了問題,做爲開發者能第一時間發現問題,修復上線是業務止損的最好方法,隨着業務飛速發展,對系統的穩定性有了更高的要求,傳統的基礎告警指標和觸發器設計方案,在使用上存在不少限制,報警規則配置依賴開發人員主觀經驗,配置一條高可用規則須要屢次實踐,等到系統或外部依賴發生變化,又要依據新的數據調整規則,不只開發筋疲力竭,報警的誤報漏報也十分嚴重,面對這些問題,咱們設計研發了一套智能報警系統,它與傳統報警最大的不一樣在於它集成了機器學習算法,具備自我學習的功能,根據系統的演進自動調節閾值,可以很大程度下降誤報和漏報。算法
數據分析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點至觸發器當前時間點的數據,理想值與真實值通過比較器處理,判斷真實值是否符合預期而對應是否觸發報警。
觸發器的設計以下圖所示:
大致上觸發器作的事是——真實值與預測值對比,不知足預期則報警。爲提升報警的準確度,經過對預測數據分時間段計算方差,方差越大則數據曲線波動越大。當波動程度大時,對應的時間段所設置的閾值應設置更寬避免較多的誤報。則當相同時間段內預測曲線 、真實曲線的均值差大於預測曲線的某個倍數時則觸發器觸發報警,這就是經過離散度和預測值獲得相對動態的閾值,咱們目前處於當前階段。
可是檢測不一樣數據類型時這個倍數不一樣,針對不一樣類型的報錯須要設定不一樣的倍數值。人工統一設定的倍數值仍是不夠準確,易形成漏報(倍數太大)或者誤報(倍數過小)。因此對於咱們來講更智能的動態閾值是能從歷史數據學習到這個動態的倍數值,這是下個階段的目標,讓波動閾值區域儘可能收的更緊湊。以下圖曲線外包裹區域:
在咱們監控系統上報數據後,基於已上報的數據咱們能夠作智能報警,而不是再像普通的報警系統,經過大量人工針對性的分段閾值設定,過於依賴人工經驗性判斷。基於機器學習的智能報警會更準確和高效。固然有了數據不僅是能夠作智能報警,這套系統還有更多可深刻挖掘和發掘的功能,智能報警只是人工智能和監控領域結合的初步成果。