前言
服務的高可用離不開穩定的監控,如果服務出現了問題,作為開發者能第一時間發現問題,修復上線是業務止損的最好方法,隨著業務飛速發展,對系統的穩定性有了更高的要求,傳統的基礎告警指標和觸發器設計方案,在使用上存在很多限制,報警規則配置依賴開發人員主觀經驗,配置一條高可用規則需要多次實踐,等到系統或外部依賴發生變化,又要依據新的資料調整規則,不僅開發筋疲力竭,報警的誤報漏報也十分嚴重,面對這些問題,我們設計研發了一套智慧報警系統,它與傳統報警最大的不同在於它整合了機器學習演算法,具有自我學習的功能,根據系統的演進自動調節閾值,能夠很大程度降低誤報和漏報。
資料分析
將某專案上報資料中的錯誤概率繪製成折線圖,可以發現一些錯誤的規律(這裡給出的是概率統計值):
從時間上看,錯誤概率時間序列有兩個明顯的特徵(如上圖所示):
- 週期性。每天錯誤量的變化趨勢都大致相同,午高峰和晚高峰樣本數大,錯誤概率穩定在一定值,凌晨到早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點至觸發器當前時間點的資料,理想值與真實值經過比較器處理,判斷真實值是否符合預期而對應是否觸發報警。
觸發器的設計如下圖所示:
大體上觸發器做的事是——真實值與預測值對比,不滿足預期則報警。為提高報警的準確度,通過對預測資料分時間段計算方差,方差越大則資料曲線波動越大。當波動程度大時,對應的時間段所設定的閾值應設定更寬避免較多的誤報。則當相同時間段內預測曲線 、真實曲線的均值差大於預測曲線的某個倍數時則觸發器觸發報警,這就是通過離散度和預測值得到相對動態的閾值,我們目前處於當前階段。
但是檢測不同資料型別時這個倍數不同,針對不同型別的報錯需要設定不同的倍數值。人工統一設定的倍數值還是不夠準確,易造成漏報(倍數太大)或者誤報(倍數太小)。所以對於我們來說更智慧的動態閾值是能從歷史資料學習到這個動態的倍數值,這是下個階段的目標,讓波動閾值區域儘量收的更緊湊。如下圖曲線外包裹區域:
總結
在我們監控系統上報資料後,基於已上報的資料我們可以做智慧報警,而不是再像普通的報警系統,通過大量人工針對性的分段閾值設定,過於依賴人工經驗性判斷。基於機器學習的智慧報警會更準確和高效。當然有了資料不只是可以做智慧報警,這套系統還有更多可深入挖掘和發掘的功能,智慧報警只是人工智慧和監控領域結合的初步成果。