<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Prometheus時(shí)序數(shù)據(jù)庫(kù)-報(bào)警的計(jì)算

          共 5152字,需瀏覽 11分鐘

           ·

          2021-03-31 13:34

          Prometheus時(shí)序數(shù)據(jù)庫(kù)-報(bào)警的計(jì)算

          在前面的文章中,筆者詳細(xì)的闡述了Prometheus的數(shù)據(jù)插入存儲(chǔ)查詢等過(guò)程。但作為一個(gè)監(jiān)控神器,報(bào)警計(jì)算功能是必不可少的。自然的Prometheus也提供了靈活強(qiáng)大的報(bào)警規(guī)則可以讓我們自由去發(fā)揮。在本篇文章里,筆者就帶讀者去看下Prometheus內(nèi)部是怎么處理報(bào)警規(guī)則的。

          報(bào)警架構(gòu)

          Prometheus只負(fù)責(zé)進(jìn)行報(bào)警計(jì)算,而具體的報(bào)警觸發(fā)則由AlertManager完成。如果我們不想改動(dòng)AlertManager以完成自定義的路由規(guī)則,還可以通過(guò)webhook外接到另一個(gè)系統(tǒng)(例如,一個(gè)轉(zhuǎn)換到kafka的程序)。

          在本篇文章里,筆者并不會(huì)去設(shè)計(jì)alertManager,而是專注于Prometheus本身報(bào)警規(guī)則的計(jì)算邏輯。

          一個(gè)最簡(jiǎn)單的報(bào)警規(guī)則

          rules:
          alert: HTTPRequestRateLow
          expr: http_requests < 100
          for: 60s
          labels:
          severity: warning
          annotations:
          description: "http request rate low"

          這上面的規(guī)則即是http請(qǐng)求數(shù)量<100持續(xù)1min,則我們開(kāi)始報(bào)警,報(bào)警級(jí)別為warning

          什么時(shí)候觸發(fā)這個(gè)計(jì)算

          在加載完規(guī)則之后,Prometheus按照evaluation_interval這個(gè)全局配置去不停的計(jì)算Rules。代碼邏輯如下所示:

          rules/manager.go

          func (g *Group) run(ctx context.Context) {
          iter := func() {
          ......
          g.Eval(ctx,evalTimestamp)
          ......
          }
          // g.interval = evaluation_interval
          tick := time.NewTicker(g.interval)
          defer tick.Stop()
          ......
          for {
          ......
          case <-tick.C:
          ......
          iter()
          }
          }

          而g.Eval的調(diào)用為:

          func (g *Group) Eval(ctx context.Context, ts time.Time) {
          // 對(duì)所有的rule
          for i, rule := range g.rules {
          ......
          // 先計(jì)算出是否有符合rule的數(shù)據(jù)
          vector, err := rule.Eval(ctx, ts, g.opts.QueryFunc, g.opts.ExternalURL)
          ......
          // 然后發(fā)送
          ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc)
          }
          ......
          }

          整個(gè)過(guò)程如下圖所示:

          對(duì)單個(gè)rule的計(jì)算

          我們可以看到,最重要的就是rule.Eval這個(gè)函數(shù)。代碼如下所示:

          func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, externalURL *url.URL) (promql.Vector, error) {
          // 最終調(diào)用了NewInstantQuery
          res, err = query(ctx,r.vector.String(),ts)
          ......
          // 報(bào)警組裝邏輯
          ......
          // active 報(bào)警狀態(tài)變遷
          }

          這個(gè)Eval包含了報(bào)警的計(jì)算/組裝/發(fā)送的所有邏輯。我們先聚焦于最重要的計(jì)算邏輯。也就是其中的query。其實(shí),這個(gè)query是對(duì)NewInstantQuery的一個(gè)簡(jiǎn)單封裝。

          func EngineQueryFunc(engine *promql.Engine, q storage.Queryable) QueryFunc {
          return func(ctx context.Context, qs string, t time.Time) (promql.Vector, error) {
          q, err := engine.NewInstantQuery(q, qs, t)
          ......
          res := q.Exec(ctx)
          }
          }

          也就是說(shuō)它執(zhí)行了一個(gè)瞬時(shí)向量的查詢。而其查詢的表達(dá)式按照我們之前給出的報(bào)警規(guī)則,即是

          http_requests < 100

          既然要計(jì)算表達(dá)式,那么第一步,肯定是將其構(gòu)造成一顆AST。其樹形結(jié)構(gòu)如下圖所示:

          解析出左節(jié)點(diǎn)是個(gè)VectorSelect而且知道了其lablelMatcher是

          __name__:http_requests

          那么我們就可以左節(jié)點(diǎn)VectorSelector進(jìn)行求值。直接利用倒排索引在head中查詢即可(因?yàn)閕nstant query的是當(dāng)前時(shí)間,所以肯定在內(nèi)存中)。

          想知道具體的計(jì)算流程,可以見(jiàn)筆者之前的博客《Prometheus時(shí)序數(shù)據(jù)庫(kù)-數(shù)據(jù)的查詢》
          計(jì)算出左節(jié)點(diǎn)的數(shù)據(jù)之后,我們就可以和右節(jié)點(diǎn)進(jìn)行比較以計(jì)算出最終結(jié)果了。具體代碼為:

          func (ev *evaluator) eval(expr Expr) Value {
          ......
          case *BinaryExpr:
          ......
          case lt == ValueTypeVector && rt == ValueTypeScalar:
          return ev.rangeEval(func(v []Value, enh *EvalNodeHelper) Vector {
          return ev.VectorscalarBinop(e.Op, v[0].(Vector), Scalar{V: v[1].(Vector)[0].Point.V}, false, e.ReturnBool, enh)
          }, e.LHS, e.RHS)
          .......
          }

          最后調(diào)用的函數(shù)即為:

          func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorMatching, returnBool bool, enh *EvalNodeHelper) Vector {
          // 對(duì)左節(jié)點(diǎn)計(jì)算出來(lái)的所有的數(shù)據(jù)sample
          for _, lhsSample := range lhs {
          ......
          // 由于左邊lv = 75 < 右邊rv = 100,且op為less
          /**
          vectorElemBinop(){
          case LESS
          return lhs, lhs < rhs
          }
          **/
          // 這邊得到的結(jié)果value=75,keep = true
          value, keep := vectorElemBinop(op, lv, rv)
          ......
          if keep {
          ......
          // 這邊就講75放到了輸出里面,也就是說(shuō)我們最后的計(jì)算確實(shí)得到了數(shù)據(jù)。
          enh.out = append(enh.out.sample)
          }
          }
          }

          如下圖所示:

          最后我們的expr輸出即為

          sample {
          Point {t:0,V:75}
          Metric {__name__:http_requests,instance:0,job:api-server}

          }

          報(bào)警狀態(tài)變遷

          計(jì)算過(guò)程講完了,筆者還稍微講一下報(bào)警的狀態(tài)變遷,也就是最開(kāi)始報(bào)警規(guī)則中的rule中的for,也即報(bào)警持續(xù)for(規(guī)則中為1min),我們才真正報(bào)警。為了實(shí)現(xiàn)這種功能,這就需要一個(gè)狀態(tài)機(jī)了。筆者這里只闡述下從Pending(報(bào)警出現(xiàn))->firing(真正發(fā)送)的邏輯。

          在之前的Eval方法里面,有下面這段

          func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, externalURL *url.URL) (promql.Vector, error) {
          for _, smpl := range res {
          ......
          if alert, ok := r.active[h]; ok && alert.State != StateInactive {
          alert.Value = smpl.V
          alert.Annotations = annotations
          continue
          }
          // 如果這個(gè)告警不在active map里面,則將其放入
          // 注意,這里的hash依舊沒(méi)有拉鏈法,有極小概率hash沖突
          r.active[h] = &Alert{
          Labels: lbs,
          Annotations: annotations,
          ActiveAt: ts,
          State: StatePending,
          Value: smpl.V,
          }
          }
          ......
          // 報(bào)警狀態(tài)的變遷邏輯
          for fp, a := range r.active {
          // 如果當(dāng)前r.active的告警已經(jīng)不在剛剛計(jì)算的result里面了 if _, ok := resultFPs[fp]; !ok {
          // 如果狀態(tài)是Pending待發(fā)送
          if a.State == StatePending || (!a.ResolvedAt.IsZero() && ts.Sub(a.ResolvedAt) > resolvedRetention) {
          delete(r.active, fp)
          }
          ......
          continue
          }
          // 對(duì)于已有的Active報(bào)警,如果其Active的時(shí)間>r.holdDuration,也就是for指定的
          if a.State == StatePending && ts.Sub(a.ActiveAt) >= r.holdDuration {
          // 我們將報(bào)警置為需要發(fā)送
          a.State = StateFiring
          a.FiredAt = ts
          }
          ......

          }
          }

          上面代碼邏輯如下圖所示:

          總結(jié)

          Prometheus作為一個(gè)監(jiān)控神器,給我們提供了各種各樣的遍歷。其強(qiáng)大的報(bào)警計(jì)算功能就是其中之一。了解其中告警的計(jì)算原理,才能讓我們更好的運(yùn)用它。


          瀏覽 63
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋在线观看资源男人网 | 午夜激情视频 | 人人看人人插 | 亚洲第一黄色 | 久久久精品麻豆 |