背景
如下憋他,我司的訂單服務(wù)的支付和訂單業(yè)務(wù)采用Prometheus配合Alertmanager來做報(bào)警處理孩饼。業(yè)務(wù)方會(huì)挑選一些比較重要的支付方式來做報(bào)警處理。大致就是一段時(shí)間內(nèi)的支付量低于某一個(gè)閾值的時(shí)候就報(bào)警竹挡。
寫成promql表達(dá)式如下
#pay_metrics就是支付的指標(biāo)名稱镀娶,pay_way就是支付方式
sum(increase(pay_metrics{pay_way="weixin"}[30m])) < 0
上面表達(dá)式的意思就是:如果微信這種支付方式的支付量在過去5分鐘內(nèi)的增量為0,那么就報(bào)警揪罕。業(yè)務(wù)方給了我們很多這種類似的報(bào)警規(guī)則梯码,然后跑了很長(zhǎng)一段時(shí)間宝泵,報(bào)警都工作得很好。
問題
某天業(yè)務(wù)收到某條報(bào)警信息轩娶,說是某某支付方式的報(bào)警有問題儿奶,他們說那個(gè)報(bào)警時(shí)間段內(nèi),他們是有支付成功的訂單的鳄抒,你這個(gè)報(bào)警有問題。然后我就會(huì)默默地查詢了up指標(biāo)(Prometheus中探活指標(biāo)),發(fā)現(xiàn)他們最近一段時(shí)間發(fā)布代碼了棒口。而他們的實(shí)例又是多實(shí)例的保屯,發(fā)布的時(shí)候是一臺(tái)臺(tái)發(fā)布的,發(fā)布之后停留在內(nèi)存中的指標(biāo)數(shù)據(jù)都會(huì)丟失贤重。然后在計(jì)算報(bào)警規(guī)則的時(shí)候可能會(huì)出現(xiàn)誤報(bào)的情況娱仔。
然后我很自豪地跟他們的研發(fā)確認(rèn)了發(fā)布的事情,然后很愉快地就去泡咖啡去了游桩。
然后還沒等我喝完咖啡牲迫,業(yè)務(wù)又找上我了,說還是有問題借卧。但是這次沒有發(fā)布盹憎。下面就開始了我得苦逼調(diào)查之旅。
調(diào)查
首先铐刘,我們明確一下問題的本質(zhì):業(yè)務(wù)在他們自己的訂單存儲(chǔ)中看到有支付訂單的陪每,但是還是報(bào)警了。其實(shí)就是業(yè)務(wù)數(shù)據(jù)和監(jiān)控報(bào)警數(shù)據(jù)不一致镰吵。那這樣的不一致就有可能是兩面的原因
- 訂單服務(wù)的研發(fā)在埋點(diǎn)支付數(shù)據(jù)的時(shí)候有bug
- Prometheus在計(jì)算報(bào)警規(guī)則的時(shí)候有問題
第一個(gè)問題檩禾,我讓研發(fā)排查了,他們只有一個(gè)入口疤祭,而且不太可能會(huì)有問題盼产。那我們先假設(shè)不是第一個(gè)問題。我們接著排查第二個(gè)問題勺馆。
Prometheus在計(jì)算的時(shí)候有問題戏售。
下面我們來審視一下下面這個(gè)函數(shù)
#pay_metrics就是支付的指標(biāo)名稱,pay_way就是支付方式
sum(increase(pay_metrics{pay_way="weixin"}[5m])) < 0
我們只用了兩個(gè)函數(shù):sum和increase函數(shù)草穆。sum比較簡(jiǎn)單灌灾,就是把指標(biāo)相加,沒什么好說的悲柱。但是increase()函數(shù)沒有想象中那么簡(jiǎn)單锋喜,至少它要處理重啟的情況。所以我就去prometheus中的issue搜搜了下豌鸡。果然被我發(fā)現(xiàn)了一個(gè)類似的issue
increase() should consider creation of new timeseries as reset
如下
大概意思就是:如果一個(gè)時(shí)間序列之前不存在然后以值1出現(xiàn)嘿般,那么這時(shí)候Prometheus就不知道計(jì)數(shù)器是實(shí)際上是增加還是第一次被簡(jiǎn)單抓取到轴总。那么increase()在處理的時(shí)候就直接返回0 了。
如下圖博个,在T1時(shí)間計(jì)算increase()的時(shí)候,m1的增量為11怀樟,m2由于向前沒有找到對(duì)應(yīng)的指標(biāo)數(shù)據(jù),所以increase()直接返回0 了盆佣,這樣在使用sum()函數(shù)計(jì)算增量和的時(shí)候就會(huì)丟失數(shù)據(jù)往堡。
下面我們來看一下實(shí)際線上的情況,我畫了兩張圖共耍,左邊的表達(dá)式是這樣的(就是報(bào)警規(guī)則中的promql)
sum(increase(pay_metrics{pay_way="weixin"}[30m]))
右邊的表達(dá)式是這樣的(計(jì)算每個(gè)時(shí)間點(diǎn)的累計(jì)量)
sum(pay_metrics{pay_way="weixin"})
從上左邊可以看到大概3點(diǎn)17分鐘左右數(shù)量為0 虑灰,這時(shí)候報(bào)警了,但是我們?cè)倏从疫叺膱D痹兜,在3點(diǎn)17分鐘的時(shí)候向前推30分鐘穆咐,這30分鐘內(nèi)實(shí)際是有數(shù)量變化的。由此可見字旭,prometheus的這兩種計(jì)算方式的計(jì)算結(jié)果是不一樣的对湃。直接sum的結(jié)果更接近于真實(shí)數(shù)據(jù)。
解決方案
那有沒有什么好的解決方案呢遗淳?很多issue提倡能不能提供一個(gè)zero_increase()函數(shù)拍柒,或者加一個(gè)可以帶默認(rèn)值的increase()函數(shù),讓用戶自己處理這種情況屈暗。我翻了下所有的issue拆讯,發(fā)現(xiàn)到目前為止官方還沒有給出一個(gè)正式的解決方法。大家有興趣可以翻看一下有關(guān)的issue
Proposal for improving rate/increase
那我們自己有沒有可能通過各種騷操作得到我們想要的效果呢养叛?反復(fù)查詢后得到一種比較容易理解且相對(duì)比較精確的解決方案种呐。如下
(sum(pay_metrics{pay_way="weixin"} or pay_metrics{pay_way="weixin"}*0) by(payMethod)-
sum(pay_metrics{pay_way="weixin"} offset 30m or pay_metrics{pay_way="weixin"}*0) by(pay_way))
思路也很簡(jiǎn)單就是sum(當(dāng)前指標(biāo))-sum(30分鐘之前的指標(biāo))就OK,然后再加上獲取不到指標(biāo)的情況下使用默認(rèn)值0來代替弃甥。
但是這種解決方案也不是完美的爽室,在重啟的情況下有可能導(dǎo)致最終計(jì)算的差值是負(fù)數(shù)。不過在報(bào)警這種場(chǎng)景下完美只要不對(duì)這種情況下報(bào)警就可以了潘飘。
總結(jié)
increase()函數(shù)其實(shí)在大多數(shù)場(chǎng)景下是沒啥問題的肮之,只在下面兩種場(chǎng)景下有可能有問題。
- 重啟時(shí)間有點(diǎn)長(zhǎng)
- 重啟后很長(zhǎng)時(shí)間某種label的指標(biāo)數(shù)據(jù)才開始增加為0
解決方案:
- 在label組合比較少的情況下卜录,可以都初始化所有l(wèi)abel組合的指標(biāo)數(shù)據(jù)為0
- 使用我們上面提到的promql表達(dá)式來解決(但是要注意重啟后計(jì)算結(jié)果為負(fù)數(shù)的情況)