Prometheus时序数据库-报警的计算

在前面的文章中,笔者详细的阐述了Prometheus的数据插入存储查询等过程。但作为一个监控神器,报警计算功能是必不可少的。自然的Prometheus也提供了灵活强大的报警规则可以让我们自由去发挥。在本篇文章里,笔者就带读者去看下Prometheus内部是怎么处理报警规则的。

报警架构

Prometheus只负责进行报警计算,而具体的报警触发则由AlertManager完成。如果我们不想改动AlertManager以完成自定义的路由规则,还可以通过webhook外接到另一个系统(例如,一个转换到kafka的程序)。



在本篇文章里,笔者并不会去设计alertManager,而是专注于Prometheus本身报警规则的计算逻辑。

一个最简单的报警规则

  1. rules:
  2. alert: HTTPRequestRateLow
  3. expr: http_requests < 100
  4. for: 60s
  5. labels:
  6. severity: warning
  7. annotations:
  8. description: "http request rate low"

这上面的规则即是http请求数量<100从持续1min,则我们开始报警,报警级别为warning

什么时候触发这个计算

在加载完规则之后,Prometheus按照evaluation_interval这个全局配置去不停的计算Rules。代码逻辑如下所示:

  1. rules/manager.go
  2. func (g *Group) run(ctx context.Context) {
  3. iter := func() {
  4. ......
  5. g.Eval(ctx,evalTimestamp)
  6. ......
  7. }
  8. // g.interval = evaluation_interval
  9. tick := time.NewTicker(g.interval)
  10. defer tick.Stop()
  11. ......
  12. for {
  13. ......
  14. case <-tick.C:
  15. ......
  16. iter()
  17. }
  18. }

而g.Eval的调用为:

  1. func (g *Group) Eval(ctx context.Context, ts time.Time) {
  2. // 对所有的rule
  3. for i, rule := range g.rules {
  4. ......
  5. // 先计算出是否有符合rule的数据
  6. vector, err := rule.Eval(ctx, ts, g.opts.QueryFunc, g.opts.ExternalURL)
  7. ......
  8. // 然后发送
  9. ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc)
  10. }
  11. ......
  12. }

整个过程如下图所示:

对单个rule的计算

我们可以看到,最重要的就是rule.Eval这个函数。代码如下所示:

  1. func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, externalURL *url.URL) (promql.Vector, error) {
  2. // 最终调用了NewInstantQuery
  3. res, err = query(ctx,r.vector.String(),ts)
  4. ......
  5. // 报警组装逻辑
  6. ......
  7. // active 报警状态变迁
  8. }

这个Eval包含了报警的计算/组装/发送的所有逻辑。我们先聚焦于最重要的计算逻辑。也就是其中的query。其实,这个query是对NewInstantQuery的一个简单封装。

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

也就是说它执行了一个瞬时向量的查询。而其查询的表达式按照我们之前给出的报警规则,即是

  1. http_requests < 100

既然要计算表达式,那么第一步,肯定是将其构造成一颗AST。其树形结构如下图所示:



解析出左节点是个VectorSelect而且知道了其lablelMatcher是

  1. __name__:http_requests

那么我们就可以左节点VectorSelector进行求值。直接利用倒排索引在head中查询即可(因为instant query的是当前时间,所以肯定在内存中)。



想知道具体的计算流程,可以见笔者之前的博客《Prometheus时序数据库-数据的查询》

计算出左节点的数据之后,我们就可以和右节点进行比较以计算出最终结果了。具体代码为:

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

最后调用的函数即为:

  1. func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorMatching, returnBool bool, enh *EvalNodeHelper) Vector {
  2. // 对左节点计算出来的所有的数据sample
  3. for _, lhsSample := range lhs {
  4. ......
  5. // 由于左边lv = 75 < 右边rv = 100,且op为less
  6. /**
  7. vectorElemBinop(){
  8. case LESS
  9. return lhs, lhs < rhs
  10. }
  11. **/
  12. // 这边得到的结果value=75,keep = true
  13. value, keep := vectorElemBinop(op, lv, rv)
  14. ......
  15. if keep {
  16. ......
  17. // 这边就讲75放到了输出里面,也就是说我们最后的计算确实得到了数据。
  18. enh.out = append(enh.out.sample)
  19. }
  20. }
  21. }

如下图所示:



最后我们的expr输出即为

  1. sample {
  2. Point {t:0,V:75}
  3. Metric {__name__:http_requests,instance:0,job:api-server}
  4. }

报警状态变迁

计算过程讲完了,笔者还稍微讲一下报警的状态变迁,也就是最开始报警规则中的rule中的for,也即报警持续for(规则中为1min),我们才真正报警。为了实现这种功能,这就需要一个状态机了。笔者这里只阐述下从Pending(报警出现)->firing(真正发送)的逻辑。

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

  1. func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, externalURL *url.URL) (promql.Vector, error) {
  2. for _, smpl := range res {
  3. ......
  4. if alert, ok := r.active[h]; ok && alert.State != StateInactive {
  5. alert.Value = smpl.V
  6. alert.Annotations = annotations
  7. continue
  8. }
  9. // 如果这个告警不在active map里面,则将其放入
  10. // 注意,这里的hash依旧没有拉链法,有极小概率hash冲突
  11. r.active[h] = &Alert{
  12. Labels: lbs,
  13. Annotations: annotations,
  14. ActiveAt: ts,
  15. State: StatePending,
  16. Value: smpl.V,
  17. }
  18. }
  19. ......
  20. // 报警状态的变迁逻辑
  21. for fp, a := range r.active {
  22. // 如果当前r.active的告警已经不在刚刚计算的result里面了 if _, ok := resultFPs[fp]; !ok {
  23. // 如果状态是Pending待发送
  24. if a.State == StatePending || (!a.ResolvedAt.IsZero() && ts.Sub(a.ResolvedAt) > resolvedRetention) {
  25. delete(r.active, fp)
  26. }
  27. ......
  28. continue
  29. }
  30. // 对于已有的Active报警,如果其Active的时间>r.holdDuration,也就是for指定的
  31. if a.State == StatePending && ts.Sub(a.ActiveAt) >= r.holdDuration {
  32. // 我们将报警置为需要发送
  33. a.State = StateFiring
  34. a.FiredAt = ts
  35. }
  36. ......
  37. }
  38. }

上面代码逻辑如下图所示:

总结

Prometheus作为一个监控神器,给我们提供了各种各样的遍历。其强大的报警计算功能就是其中之一。了解其中告警的计算原理,才能让我们更好的运用它。

Prometheus时序数据库-报警的计算的更多相关文章

  1. Prometheus时序数据库-数据的查询

    Prometheus时序数据库-数据的查询 前言 在之前的博客里,笔者详细阐述了Prometheus数据的插入过程.但我们最常见的打交道的是数据的查询.Prometheus提供了强大的Promql来满 ...

  2. Prometheus时序数据库-内存中的存储结构

    Prometheus时序数据库-内存中的存储结构 前言 笔者最近担起了公司监控的重任,而当前监控最流行的数据库即是Prometheus.按照笔者打破砂锅问到底的精神,自然要把这个开源组件源码搞明白才行 ...

  3. Prometheus时序数据库-磁盘中的存储结构

    Prometheus时序数据库-磁盘中的存储结构 前言 之前的文章里,笔者详细描述了监控数据在Prometheus内存中的结构.而其在磁盘中的存储结构,也是非常有意思的,关于这部分内容,将在本篇文章进 ...

  4. Prometheus时序数据库-数据的插入

    Prometheus时序数据库-数据的插入 前言 在之前的文章里,笔者详细的阐述了Prometheus时序数据库在内存和磁盘中的存储结构.有了前面的铺垫,笔者就可以在本篇文章阐述下数据的插入过程. 监 ...

  5. 时序数据库连载系列:指标届的独角兽Prometheus

    简介 Prometheus是SoundCloud公司开发的一站式监控告警平台,依赖少,功能齐全.于2016年加入CNCF,广泛用于 Kubernetes集群的监控系统中,2018.8月成为继K8S之后 ...

  6. 时序数据库(TSDB)-为万物互联插上一双翅膀

    本文由  网易云发布. 时序数据库(TSDB)是一种特定类型的数据库,主要用来存储时序数据.随着5G技术的不断成熟,物联网技术将会使得万物互联.物联网时代之前只有手机.电脑可以联网,以后所有设备都会联 ...

  7. 时序数据库TDengine 详细安装+集成流程+问题解决

    官方文档:https://docs.taosdata.com/get-started/package/ 点击进入 产品简介 TDengine 是一款高性能.分布式.支持 SQL 的时序数据库 (Dat ...

  8. 互联网级监控系统必备-时序数据库之Influxdb

    时间序列数据库,简称时序数据库,Time Series Database,一个全新的领域,最大的特点就是每个条数据都带有Time列. 时序数据库到底能用到什么业务场景,答案是:监控系统. Baidu一 ...

  9. 日吞吐万亿,腾讯云时序数据库CTSDB解密

    一.背景 随着移动互联网.物联网.大数据等行业的高速发展,数据在持续的以指数级的速度增长,比如我们使用手机访问互网络时的行为数据,各种可穿戴设备上报的状态数据,工厂中设备传感器采集的指标数据,传统互联 ...

随机推荐

  1. 技术分享: CSS3 系列

    技术分享: CSS3 系列 css 一键换肤 css 打印样式,媒体查询 css 禁用选择 css 性能优化 css 计算单位 css 3D 特效 refs xgqfrms 2012-2020 www ...

  2. Koa 洋葱模型

    Koa 洋葱模型 let context = { data: [] }; async function middleware1(ctx, next) { console.log('action 001 ...

  3. web 语音播报 & 网页阅读器

    web 语音播报 & 网页阅读器 Chrome auto speech & voice speaking http://3.141592653589793238462643383279 ...

  4. TypeScript 3.7 RC & Nullish Coalescing

    TypeScript 3.7 RC & Nullish Coalescing null, undefined default value https://devblogs.microsoft. ...

  5. CORS & OPTIONS & preflight request

    CORS & OPTIONS preflight request CORS 原理 CORS跨域的原理实际上是浏览器与服务器通过一些HTTP协议头来做一些约定和限制 OPTIONS 应用场景 h ...

  6. 揭秘高倍矿币 Baccarat BGV,为何NGK DeFi的财富效应如此神奇?

    作为区块链4.0代表的NGK公链,这次也将借助它自己的DeFi版块NGK Baccarat,开启属于它自己的千倍财富之旅. 如果说,比特币能让没有银行账户的人,可以在全球任何时间.地点都能自由进行交易 ...

  7. redis源码之dict

    大家都知道redis默认是16个db,但是这些db底层的设计结构是什么样的呢? 我们来简单的看一下源码,重要的字段都有所注释 typedef struct redisDb { dict *dict; ...

  8. Vue学习笔记-chrome84版本浏览器跨域设置

    一  使用环境: windows 7 64位操作系统 二  chrome84版本浏览器跨域设置   报错问题:Indicate whether to send a cookie in a cross- ...

  9. java算法题

    1.下面输出结果是什么? public class Test { public static void main(String[] args) { Person person=new Person(& ...

  10. c++指针类型的函数

    下面随笔将讲述c++指针类型的函数. 原创链接:https://www.cnblogs.com/iFrank/p/14444379.html 指针类型的函数 若函数的返回值是指针,该函数就是指针类型的 ...