QuXiao's Blog

Life && Tech && Thoughts

Open-Falcon 源码阅读(四) Judge

Written on

Judge

Judge是用于判断是否触发报警条件的组件。 Transfer的数据不但要打到Graph来存储并绘图,还要打到Judge用于报警判断。Judge先从Hbs获取所有策略列表,静等Transfer的数据转发。 每收到一条Transfer转发过来的数据,立即找到这条数据关联的Strategy、Expression,然后做阈值判断。

策略

如之前在Hbs章节所说,Open-Falcon中的策略分成两类:Strategy和Expression。Strategy是需要与主机组进行关联、作用在主机数据上面的;而Expression则不需要有任何关联,是对全局范围内进行的一个过滤和判断。

如何找到关联的Strategy

push上来的数据带有一个endpoint,endpoint通常都是hostname,hostname隶属于多个HostGroup,HostGroup可以关联多个Template,各个Teamplate下面就是Strategy,层层顺藤摸瓜可得。但是,如果endpoint不是hostname,并没有被HostGroup管理,那就找不到了。

如何找到关联的Expression

这是一种更通用的方案,主要针对endpoint不是hostname的情况。push上来的数据通常带有多个tag,比如project=falcon,module=judge, 假如我们要针对所有打了project=falcon这个tag的数据做qps的阈值判断,那我们可以配置一个这样的表达式:

each(metric=qps project=falcon)

如上配置之后,push上来的数据如果发现metric=qps,并且带有project=falcon这个tag,那就说明与这个expression相关,要做相关阈值判断。

数据组织

Agent上报的数据从Transfer模块打过来之后,直接保存在一个叫HistoryBigMap的映射中,以下是其对应的初始化部分:

// 这是个线程不安全的大Map,需要提前初始化好
var HistoryBigMap = make(map[string]*JudgeItemMap)

func InitHistoryBigMap() {
        arr := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}
        for i := 0; i < 16; i++ {
                for j := 0; j < 16; j++ {
                        HistoryBigMap[arr[i]+arr[j]] = NewJudgeItemMap()
                }
        }
}

type JudgeItemMap struct {
        sync.RWMutex
        M map[string]*SafeLinkedList
}

func NewJudgeItemMap() *JudgeItemMap {
        return &JudgeItemMap{M: make(map[string]*SafeLinkedList)}
}

可以看出,HistoryBigMap一共有16*16个key,这些key对应的是Agent上报的数据(item)的key的前两位,对应的value又是一个map,这个map的key对应的是item的key,value则是一个链表,用于保存该项指标的最近数据,进而进行异常判断。

异常判断与发送告警

判断Strategy

先计算出key(由endpoint + metric组成),通过这个key就可以找到对应的strategy有哪些了,通过Strategy、以及从HistoryBigMap中找出的最近的数据链表,就可以进行策略的判断了,有了判断结果之后,还会检查如下条件:

  • 检查的数据个数是否判断条件(例如检查最近5份数据,但是目前只有3份)
  • 该策略是否被屏蔽(maxStep设置为0)
  • 当前报警次数是否已经达到最大告警次数
  • 距离上次报警是否满足一定的时间间隔

条件均满足之后,就会生成一个event。Event的格式如下:

// 机器监控和实例监控都会产生Event,共用这么一个struct
type Event struct {
        Id          string            `json:"id"`
        Strategy    *Strategy         `json:"strategy"`
        Expression  *Expression       `json:"expression"`
        Status      string            `json:"status"` // OK or PROBLEM
        Endpoint    string            `json:"endpoint"`
        LeftValue   float64           `json:"leftValue"`
        CurrentStep int               `json:"currentStep"`
        EventTime   int64             `json:"eventTime"`
        PushedTags  map[string]string `json:"pushedTags"`
}

对于Strategy来说,event.id由strategy.id + item.pk()组成。最后的发送告警事件,是将该event结构通过LPUSH发送至redis list,具体告警发送给哪些设备,是由下游模块来进行处理。

一些优缺点

支持报警间隔以及最大报警次数,这样可以防止『报警风暴』,太多的报警等于没有报警。

这个Expression概念的策略,能够不跟主机或者主机组绑定起来,从一个整体的方向去判断某项综合指标是否异常,这个设计挺不错的。

不过,Judge模块还有些地方感觉可以改进:

策略判断是按照例如『最近3次的平均值』这样的表达式形式,只要按照指标数量,而没有像绝大多数的监控系统那样按照时间来进行计算(例如最近5分钟的平均值);

此外,如果是按照最近的指标数量,那么如果加上个极端值过滤的功能就更好了;

策略都是基于某一项指标来进行判断的,无法判断综合性的指标。虽然可以使用自定义监控来代替其中的一些,但是例如『可用率』、『错误率』这样的指标,可能需要判断一段时间内多项指标的一个计算值,比如

错误率 = 错误样本数 / 总样本数

按照目前Judge的设计,应该是无法满足的。

-- EOF --

comments powered by Disqus